001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.felix.framework;
020
021 import java.util.*;
022
023 import org.apache.felix.framework.util.FelixConstants;
024 import org.osgi.framework.*;
025 import org.osgi.framework.hooks.service.*;
026 import org.osgi.framework.launch.Framework;
027
028 public class ServiceRegistry
029 {
030 private final Logger m_logger;
031 private long m_currentServiceId = 1L;
032 // Maps bundle to an array of service registrations.
033 private final Map m_serviceRegsMap = Collections.synchronizedMap(new HashMap());
034 // Maps registration to thread to keep track when a
035 // registration is in use, which will cause other
036 // threads to wait.
037 private Map m_lockedRegsMap = new HashMap();
038 // Maps bundle to an array of usage counts.
039 private Map m_inUseMap = new HashMap();
040
041 private final ServiceRegistryCallbacks m_callbacks;
042
043 private final Set m_eventHooks = new TreeSet(Collections.reverseOrder());
044 private final Set m_findHooks = new TreeSet(Collections.reverseOrder());
045 private final Set m_listenerHooks = new TreeSet(Collections.reverseOrder());
046
047 public ServiceRegistry(Logger logger, ServiceRegistryCallbacks callbacks)
048 {
049 m_logger = logger;
050 m_callbacks = callbacks;
051 }
052
053 public ServiceReference[] getRegisteredServices(Bundle bundle)
054 {
055 ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
056 if (regs != null)
057 {
058 List refs = new ArrayList(regs.length);
059 for (int i = 0; i < regs.length; i++)
060 {
061 try
062 {
063 refs.add(regs[i].getReference());
064 }
065 catch (IllegalStateException ex)
066 {
067 // Don't include the reference as it is not valid anymore
068 }
069 }
070 return (ServiceReference[]) refs.toArray(new ServiceReference[refs.size()]);
071 }
072 return null;
073 }
074
075 public ServiceRegistration registerService(
076 Bundle bundle, String[] classNames, Object svcObj, Dictionary dict)
077 {
078 ServiceRegistration reg = null;
079
080 synchronized (this)
081 {
082 // Create the service registration.
083 reg = new ServiceRegistrationImpl(
084 this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict);
085
086 // Keep track of registered hooks.
087 addHooks(classNames, svcObj, reg.getReference());
088
089 // Get the bundles current registered services.
090 ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
091 m_serviceRegsMap.put(bundle, addServiceRegistration(regs, reg));
092 }
093
094 // Notify callback objects about registered service.
095 if (m_callbacks != null)
096 {
097 m_callbacks.serviceChanged(
098 new ServiceEvent(ServiceEvent.REGISTERED, reg.getReference()), null);
099 }
100 return reg;
101 }
102
103 public void unregisterService(Bundle bundle, ServiceRegistration reg)
104 {
105 // If this is a hook, it should be removed.
106 removeHook(reg.getReference());
107
108 synchronized (this)
109 {
110 // Note that we don't lock the service registration here using
111 // the m_lockedRegsMap because we want to allow bundles to get
112 // the service during the unregistration process. However, since
113 // we do remove the registration from the service registry, no
114 // new bundles will be able to look up the service.
115
116 // Now remove the registered service.
117 ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
118 m_serviceRegsMap.put(bundle, removeServiceRegistration(regs, reg));
119 }
120
121 // Notify callback objects about unregistering service.
122 if (m_callbacks != null)
123 {
124 m_callbacks.serviceChanged(
125 new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()), null);
126 }
127
128 // Now forcibly unget the service object for all stubborn clients.
129 synchronized (this)
130 {
131 Bundle[] clients = getUsingBundles(reg.getReference());
132 for (int i = 0; (clients != null) && (i < clients.length); i++)
133 {
134 while (ungetService(clients[i], reg.getReference()))
135 ; // Keep removing until it is no longer possible
136 }
137 ((ServiceRegistrationImpl) reg).invalidate();
138 }
139 }
140
141 /**
142 * This method retrieves all services registrations for the specified
143 * bundle and invokes <tt>ServiceRegistration.unregister()</tt> on each
144 * one. This method is only called be the framework to clean up after
145 * a stopped bundle.
146 * @param bundle the bundle whose services should be unregistered.
147 **/
148 public void unregisterServices(Bundle bundle)
149 {
150 // Simply remove all service registrations for the bundle.
151 ServiceRegistration[] regs = null;
152 synchronized (this)
153 {
154 regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
155 }
156
157 // Note, there is no race condition here with respect to the
158 // bundle registering more services, because its bundle context
159 // has already been invalidated by this point, so it would not
160 // be able to register more services.
161
162 // Unregister each service.
163 for (int i = 0; (regs != null) && (i < regs.length); i++)
164 {
165 if (((ServiceRegistrationImpl) regs[i]).isValid())
166 {
167 regs[i].unregister();
168 }
169 }
170
171 // Now remove the bundle itself.
172 synchronized (this)
173 {
174 m_serviceRegsMap.remove(bundle);
175 }
176 }
177
178 public List getServiceReferences(String className, Filter filter)
179 {
180 // Create a filtered list of service references.
181 List list = new ArrayList();
182
183 Object[] registrations = m_serviceRegsMap.values().toArray();
184
185 // Iterator over all service registrations.
186 for (int i = 0; i < registrations.length; i++)
187 {
188 ServiceRegistration[] regs = (ServiceRegistration[]) registrations[i];
189
190 for (int regIdx = 0;
191 (regs != null) && (regIdx < regs.length);
192 regIdx++)
193 {
194 try
195 {
196 // Determine if the registered services matches
197 // the search criteria.
198 boolean matched = false;
199
200 // If className is null, then look at filter only.
201 if ((className == null) &&
202 ((filter == null) || filter.match(regs[regIdx].getReference())))
203 {
204 matched = true;
205 }
206 // If className is not null, then first match the
207 // objectClass property before looking at the
208 // filter.
209 else if (className != null)
210 {
211 String[] objectClass = (String[])
212 ((ServiceRegistrationImpl) regs[regIdx])
213 .getProperty(FelixConstants.OBJECTCLASS);
214 for (int classIdx = 0;
215 classIdx < objectClass.length;
216 classIdx++)
217 {
218 if (objectClass[classIdx].equals(className) &&
219 ((filter == null) || filter.match(regs[regIdx].getReference())))
220 {
221 matched = true;
222 break;
223 }
224 }
225 }
226
227 // Add reference if it was a match.
228 if (matched)
229 {
230 list.add(regs[regIdx].getReference());
231 }
232 }
233 catch (IllegalStateException ex)
234 {
235 // Don't include the reference as it is not valid anymore
236 }
237 }
238 }
239
240 return list;
241 }
242
243 public synchronized ServiceReference[] getServicesInUse(Bundle bundle)
244 {
245 UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
246 if (usages != null)
247 {
248 ServiceReference[] refs = new ServiceReference[usages.length];
249 for (int i = 0; i < refs.length; i++)
250 {
251 refs[i] = usages[i].m_ref;
252 }
253 return refs;
254 }
255 return null;
256 }
257
258 public Object getService(Bundle bundle, ServiceReference ref)
259 {
260 UsageCount usage = null;
261 Object svcObj = null;
262
263 // Get the service registration.
264 ServiceRegistrationImpl reg =
265 ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
266
267 synchronized (this)
268 {
269 // First make sure that no existing operation is currently
270 // being performed by another thread on the service registration.
271 for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg))
272 {
273 // We don't allow cycles when we call out to the service factory.
274 if (o.equals(Thread.currentThread()))
275 {
276 throw new IllegalStateException(
277 "ServiceFactory.getService() resulted in a cycle.");
278 }
279
280 // Otherwise, wait for it to be freed.
281 try
282 {
283 wait();
284 }
285 catch (InterruptedException ex)
286 {
287 }
288 }
289
290 // Lock the service registration.
291 m_lockedRegsMap.put(reg, Thread.currentThread());
292
293 // Make sure the service registration is still valid.
294 if (reg.isValid())
295 {
296 // Get the usage count, if any.
297 usage = getUsageCount(bundle, ref);
298
299 // If we don't have a usage count, then create one and
300 // since the spec says we increment usage count before
301 // actually getting the service object.
302 if (usage == null)
303 {
304 usage = addUsageCount(bundle, ref);
305 }
306
307 // Increment the usage count and grab the already retrieved
308 // service object, if one exists.
309 usage.m_count++;
310 svcObj = usage.m_svcObj;
311 }
312 }
313
314 // If we have a usage count, but no service object, then we haven't
315 // cached the service object yet, so we need to create one now without
316 // holding the lock, since we will potentially call out to a service
317 // factory.
318 try
319 {
320 if ((usage != null) && (svcObj == null))
321 {
322 svcObj = reg.getService(bundle);
323 }
324 }
325 finally
326 {
327 // If we successfully retrieved a service object, then we should
328 // cache it in the usage count. If not, we should flush the usage
329 // count. Either way, we need to unlock the service registration
330 // so that any threads waiting for it can continue.
331 synchronized (this)
332 {
333 // Before caching the service object, double check to see if
334 // the registration is still valid, since it may have been
335 // unregistered while we didn't hold the lock.
336 if (!reg.isValid() || (svcObj == null))
337 {
338 flushUsageCount(bundle, ref);
339 }
340 else
341 {
342 usage.m_svcObj = svcObj;
343 }
344 m_lockedRegsMap.remove(reg);
345 notifyAll();
346 }
347 }
348
349 return svcObj;
350 }
351
352 public boolean ungetService(Bundle bundle, ServiceReference ref)
353 {
354 UsageCount usage = null;
355 ServiceRegistrationImpl reg =
356 ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
357
358 synchronized (this)
359 {
360 // First make sure that no existing operation is currently
361 // being performed by another thread on the service registration.
362 for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg))
363 {
364 // We don't allow cycles when we call out to the service factory.
365 if (o.equals(Thread.currentThread()))
366 {
367 throw new IllegalStateException(
368 "ServiceFactory.ungetService() resulted in a cycle.");
369 }
370
371 // Otherwise, wait for it to be freed.
372 try
373 {
374 wait();
375 }
376 catch (InterruptedException ex)
377 {
378 }
379 }
380
381 // Get the usage count.
382 usage = getUsageCount(bundle, ref);
383 // If there is no cached services, then just return immediately.
384 if (usage == null)
385 {
386 return false;
387 }
388
389 // Lock the service registration.
390 m_lockedRegsMap.put(reg, Thread.currentThread());
391 }
392
393 // If usage count will go to zero, then unget the service
394 // from the registration; we do this outside the lock
395 // since this might call out to the service factory.
396 try
397 {
398 if (usage.m_count == 1)
399 {
400 // Remove reference from usages array.
401 ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
402 .getRegistration().ungetService(bundle, usage.m_svcObj);
403 }
404 }
405 finally
406 {
407 // Finally, decrement usage count and flush if it goes to zero or
408 // the registration became invalid while we were not holding the
409 // lock. Either way, unlock the service registration so that any
410 // threads waiting for it can continue.
411 synchronized (this)
412 {
413 // Decrement usage count, which spec says should happen after
414 // ungetting the service object.
415 usage.m_count--;
416
417 // If the registration is invalid or the usage count has reached
418 // zero, then flush it.
419 if (!reg.isValid() || (usage.m_count <= 0))
420 {
421 usage.m_svcObj = null;
422 flushUsageCount(bundle, ref);
423 }
424
425 // Release the registration lock so any waiting threads can
426 // continue.
427 m_lockedRegsMap.remove(reg);
428 notifyAll();
429 }
430 }
431
432 return true;
433 }
434
435
436 /**
437 * This is a utility method to release all services being
438 * used by the specified bundle.
439 * @param bundle the bundle whose services are to be released.
440 **/
441 public void ungetServices(Bundle bundle)
442 {
443 UsageCount[] usages;
444 synchronized (this)
445 {
446 usages = (UsageCount[]) m_inUseMap.get(bundle);
447 }
448
449 if (usages == null)
450 {
451 return;
452 }
453
454 // Note, there is no race condition here with respect to the
455 // bundle using more services, because its bundle context
456 // has already been invalidated by this point, so it would not
457 // be able to look up more services.
458
459 // Remove each service object from the
460 // service cache.
461 for (int i = 0; i < usages.length; i++)
462 {
463 // Keep ungetting until all usage count is zero.
464 while (ungetService(bundle, usages[i].m_ref))
465 {
466 // Empty loop body.
467 }
468 }
469 }
470
471 public synchronized Bundle[] getUsingBundles(ServiceReference ref)
472 {
473 Bundle[] bundles = null;
474 for (Iterator iter = m_inUseMap.entrySet().iterator(); iter.hasNext(); )
475 {
476 Map.Entry entry = (Map.Entry) iter.next();
477 Bundle bundle = (Bundle) entry.getKey();
478 UsageCount[] usages = (UsageCount[]) entry.getValue();
479 for (int useIdx = 0; useIdx < usages.length; useIdx++)
480 {
481 if (usages[useIdx].m_ref.equals(ref))
482 {
483 // Add the bundle to the array to be returned.
484 if (bundles == null)
485 {
486 bundles = new Bundle[] { bundle };
487 }
488 else
489 {
490 Bundle[] nbs = new Bundle[bundles.length + 1];
491 System.arraycopy(bundles, 0, nbs, 0, bundles.length);
492 nbs[bundles.length] = bundle;
493 bundles = nbs;
494 }
495 }
496 }
497 }
498 return bundles;
499 }
500
501 void servicePropertiesModified(ServiceRegistration reg, Dictionary oldProps)
502 {
503 if (m_callbacks != null)
504 {
505 m_callbacks.serviceChanged(
506 new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()), oldProps);
507 }
508 }
509
510 public Logger getLogger()
511 {
512 return m_logger;
513 }
514
515 private static ServiceRegistration[] addServiceRegistration(
516 ServiceRegistration[] regs, ServiceRegistration reg)
517 {
518 if (regs == null)
519 {
520 regs = new ServiceRegistration[] { reg };
521 }
522 else
523 {
524 ServiceRegistration[] newRegs = new ServiceRegistration[regs.length + 1];
525 System.arraycopy(regs, 0, newRegs, 0, regs.length);
526 newRegs[regs.length] = reg;
527 regs = newRegs;
528 }
529 return regs;
530 }
531
532 private static ServiceRegistration[] removeServiceRegistration(
533 ServiceRegistration[] regs, ServiceRegistration reg)
534 {
535 for (int i = 0; (regs != null) && (i < regs.length); i++)
536 {
537 if (regs[i].equals(reg))
538 {
539 // If this is the only usage, then point to empty list.
540 if ((regs.length - 1) == 0)
541 {
542 regs = new ServiceRegistration[0];
543 }
544 // Otherwise, we need to do some array copying.
545 else
546 {
547 ServiceRegistration[] newRegs = new ServiceRegistration[regs.length - 1];
548 System.arraycopy(regs, 0, newRegs, 0, i);
549 if (i < newRegs.length)
550 {
551 System.arraycopy(
552 regs, i + 1, newRegs, i, newRegs.length - i);
553 }
554 regs = newRegs;
555 }
556 }
557 }
558 return regs;
559 }
560
561 /**
562 * Utility method to retrieve the specified bundle's usage count for the
563 * specified service reference.
564 * @param bundle The bundle whose usage counts are being searched.
565 * @param ref The service reference to find in the bundle's usage counts.
566 * @return The associated usage count or null if not found.
567 **/
568 private UsageCount getUsageCount(Bundle bundle, ServiceReference ref)
569 {
570 UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
571 for (int i = 0; (usages != null) && (i < usages.length); i++)
572 {
573 if (usages[i].m_ref.equals(ref))
574 {
575 return usages[i];
576 }
577 }
578 return null;
579 }
580
581 /**
582 * Utility method to update the specified bundle's usage count array to
583 * include the specified service. This method should only be called
584 * to add a usage count for a previously unreferenced service. If the
585 * service already has a usage count, then the existing usage count
586 * counter simply needs to be incremented.
587 * @param bundle The bundle acquiring the service.
588 * @param ref The service reference of the acquired service.
589 * @param svcObj The service object of the acquired service.
590 **/
591 private UsageCount addUsageCount(Bundle bundle, ServiceReference ref)
592 {
593 UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
594
595 UsageCount usage = new UsageCount();
596 usage.m_ref = ref;
597
598 if (usages == null)
599 {
600 usages = new UsageCount[] { usage };
601 }
602 else
603 {
604 UsageCount[] newUsages = new UsageCount[usages.length + 1];
605 System.arraycopy(usages, 0, newUsages, 0, usages.length);
606 newUsages[usages.length] = usage;
607 usages = newUsages;
608 }
609
610 m_inUseMap.put(bundle, usages);
611
612 return usage;
613 }
614
615 /**
616 * Utility method to flush the specified bundle's usage count for the
617 * specified service reference. This should be called to completely
618 * remove the associated usage count object for the specified service
619 * reference. If the goal is to simply decrement the usage, then get
620 * the usage count and decrement its counter. This method will also
621 * remove the specified bundle from the "in use" map if it has no more
622 * usage counts after removing the usage count for the specified service
623 * reference.
624 * @param bundle The bundle whose usage count should be removed.
625 * @param ref The service reference whose usage count should be removed.
626 **/
627 private void flushUsageCount(Bundle bundle, ServiceReference ref)
628 {
629 UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
630 for (int i = 0; (usages != null) && (i < usages.length); i++)
631 {
632 if (usages[i].m_ref.equals(ref))
633 {
634 // If this is the only usage, then point to empty list.
635 if ((usages.length - 1) == 0)
636 {
637 usages = null;
638 }
639 // Otherwise, we need to do some array copying.
640 else
641 {
642 UsageCount[] newUsages = new UsageCount[usages.length - 1];
643 System.arraycopy(usages, 0, newUsages, 0, i);
644 if (i < newUsages.length)
645 {
646 System.arraycopy(
647 usages, i + 1, newUsages, i, newUsages.length - i);
648 }
649 usages = newUsages;
650 }
651 }
652 }
653
654 if (usages != null)
655 {
656 m_inUseMap.put(bundle, usages);
657 }
658 else
659 {
660 m_inUseMap.remove(bundle);
661 }
662 }
663
664 private void addHooks(String[] classNames, Object svcObj, ServiceReference ref)
665 {
666 if (isHook(classNames, EventHook.class, svcObj))
667 {
668 synchronized (m_eventHooks)
669 {
670 m_eventHooks.add(ref);
671 }
672 }
673
674 if (isHook(classNames, FindHook.class, svcObj))
675 {
676 synchronized (m_findHooks)
677 {
678 m_findHooks.add(ref);
679 }
680 }
681
682 if (isHook(classNames, ListenerHook.class, svcObj))
683 {
684 synchronized (m_listenerHooks)
685 {
686 m_listenerHooks.add(ref);
687 }
688 }
689 }
690
691 static boolean isHook(String[] classNames, Class hookClass, Object svcObj)
692 {
693 if (svcObj instanceof ServiceFactory)
694 {
695 return Arrays.asList(classNames).contains(hookClass.getName());
696 }
697
698 if (hookClass.isAssignableFrom(svcObj.getClass()))
699 {
700 String hookName = hookClass.getName();
701 for (int i = 0; i < classNames.length; i++)
702 {
703 if (classNames[i].equals(hookName))
704 {
705 return true;
706 }
707 }
708 }
709 return false;
710 }
711
712 private void removeHook(ServiceReference ref)
713 {
714 Object svcObj = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
715 .getRegistration().getService();
716 String [] classNames = (String[]) ref.getProperty(Constants.OBJECTCLASS);
717
718 if (isHook(classNames, EventHook.class, svcObj))
719 {
720 synchronized (m_eventHooks)
721 {
722 m_eventHooks.remove(ref);
723 }
724 }
725
726 if (isHook(classNames, FindHook.class, svcObj))
727 {
728 synchronized (m_findHooks)
729 {
730 m_findHooks.remove(ref);
731 }
732 }
733
734 if (isHook(classNames, ListenerHook.class, svcObj))
735 {
736 synchronized (m_listenerHooks)
737 {
738 m_listenerHooks.remove(ref);
739 }
740 }
741 }
742
743 public List getEventHooks()
744 {
745 synchronized (m_eventHooks)
746 {
747 return new ArrayList(m_eventHooks);
748 }
749 }
750
751 List getFindHooks()
752 {
753 synchronized (m_findHooks)
754 {
755 return new ArrayList(m_findHooks);
756 }
757 }
758
759 List getListenerHooks()
760 {
761 synchronized (m_listenerHooks)
762 {
763 return new ArrayList(m_listenerHooks);
764 }
765 }
766
767 /**
768 * Invokes a Service Registry Hook
769 * @param ref The ServiceReference associated with the hook to be invoked, the hook
770 * service object will be obtained through this object.
771 * @param framework The framework that is invoking the hook, typically the Felix object.
772 * @param callback This is a callback object that is invoked with the actual hook object to
773 * be used, either the plain hook, or one obtained from the ServiceFactory.
774 */
775 public void invokeHook(
776 ServiceReference ref, Framework framework, InvokeHookCallback callback)
777 {
778 Object hook = getService(framework, ref);
779
780 try
781 {
782 callback.invokeHook(hook);
783 }
784 catch (Throwable th)
785 {
786 m_logger.log(ref, Logger.LOG_WARNING,
787 "Problem invoking Service Registry Hook", th);
788 }
789 finally
790 {
791 ungetService(framework, ref);
792 }
793 }
794
795 private static class UsageCount
796 {
797 public int m_count = 0;
798 public ServiceReference m_ref = null;
799 public Object m_svcObj = null;
800 }
801
802 public interface ServiceRegistryCallbacks
803 {
804 void serviceChanged(ServiceEvent event, Dictionary oldProps);
805 }
806 }