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    }