001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    /*
019     * Copyright (c) OSGi Alliance (2007, 2008). All Rights Reserved.
020     * 
021     * Licensed under the Apache License, Version 2.0 (the "License");
022     * you may not use this file except in compliance with the License.
023     * You may obtain a copy of the License at
024     *
025     *      http://www.apache.org/licenses/LICENSE-2.0
026     *
027     * Unless required by applicable law or agreed to in writing, software
028     * distributed under the License is distributed on an "AS IS" BASIS,
029     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
030     * See the License for the specific language governing permissions and
031     * limitations under the License.
032     */
033    
034    package org.apache.camel.impl.osgi.tracker;
035    
036    import org.osgi.framework.Bundle;
037    import org.osgi.framework.BundleContext;
038    import org.osgi.framework.BundleEvent;
039    import org.osgi.framework.SynchronousBundleListener;
040    
041    /**
042     * The <code>BundleTracker</code> class simplifies tracking bundles much like
043     * the <code>ServiceTracker</code> simplifies tracking services.
044     * <p>
045     * A <code>BundleTracker</code> is constructed with state criteria and a
046     * <code>BundleTrackerCustomizer</code> object. A <code>BundleTracker</code> can
047     * use the <code>BundleTrackerCustomizer</code> to select which bundles are
048     * tracked and to create a customized object to be tracked with the bundle. The
049     * <code>BundleTracker</code> can then be opened to begin tracking all bundles
050     * whose state matches the specified state criteria.
051     * <p>
052     * The <code>getBundles</code> method can be called to get the
053     * <code>Bundle</code> objects of the bundles being tracked. The
054     * <code>getObject</code> method can be called to get the customized object for
055     * a tracked bundle.
056     * <p>
057     * The <code>BundleTracker</code> class is thread-safe. It does not call a
058     * <code>BundleTrackerCustomizer</code> while holding any locks.
059     * <code>BundleTrackerCustomizer</code> implementations must also be
060     * thread-safe.
061     * 
062     * @ThreadSafe
063     * @version 
064     * @since 1.4
065     */
066    public class BundleTracker implements BundleTrackerCustomizer {
067        /* set this to true to compile in debug messages */
068        static final boolean DEBUG = false;
069     
070        /**
071         * The Bundle Context used by this <code>BundleTracker</code>.
072         */
073        protected final BundleContext context;
074        
075        /**
076         * State mask for bundles being tracked. This field contains the ORed values
077         * of the bundle states being tracked.
078         */
079        final int mask;
080        
081        /**
082         * The <code>BundleTrackerCustomizer</code> object for this tracker.
083         */
084        final BundleTrackerCustomizer customizer;
085    
086        /**
087         * Tracked bundles: <code>Bundle</code> object -> customized Object and
088         * <code>BundleListener</code> object
089         */
090        private volatile Tracked tracked;
091    
092        /**
093         * Create a <code>BundleTracker</code> for bundles whose state is present in
094         * the specified state mask.
095         * <p>
096         * Bundles whose state is present on the specified state mask will be
097         * tracked by this <code>BundleTracker</code>.
098         * 
099         * @param context The <code>BundleContext</code> against which the tracking
100         *            is done.
101         * @param stateMask The bit mask of the <code>OR</code>ing of the bundle
102         *            states to be tracked.
103         * @param customizer The customizer object to call when bundles are added,
104         *            modified, or removed in this <code>BundleTracker</code>. If
105         *            customizer is <code>null</code>, then this
106         *            <code>BundleTracker</code> will be used as the
107         *            <code>BundleTrackerCustomizer</code> and this
108         *            <code>BundleTracker</code> will call the
109         *            <code>BundleTrackerCustomizer</code> methods on itself.
110         * @see Bundle#getState()
111         */
112        public BundleTracker(BundleContext context, int stateMask, BundleTrackerCustomizer customizer) {
113            this.context = context;
114            this.mask = stateMask;
115            this.customizer = (customizer == null) ? this : customizer;
116        }
117        
118        /**
119         * Accessor method for the current Tracked object. This method is only
120         * intended to be used by the unsynchronized methods which do not modify the
121         * tracked field.
122         * 
123         * @return The current Tracked object.
124         */
125        private Tracked tracked() {
126            return tracked;
127        }
128    
129    
130        /**
131         * Open this <code>BundleTracker</code> and begin tracking bundles.
132         * <p>
133         * Bundle which match the state criteria specified when this
134         * <code>BundleTracker</code> was created are now tracked by this
135         * <code>BundleTracker</code>.
136         * 
137         * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
138         *             with which this <code>BundleTracker</code> was created is no
139         *             longer valid.
140         * @throws java.lang.SecurityException If the caller and this class do not
141         *             have the appropriate
142         *             <code>AdminPermission[context bundle,LISTENER]</code>, and
143         *             the Java Runtime Environment supports permissions.
144         */
145        public void open() {
146            final Tracked t;
147            synchronized (this) {
148                if (tracked != null) {
149                    return;
150                }
151                if (DEBUG) {
152                    System.out.println("BundleTracker.open");
153                }
154                t = new Tracked();
155                synchronized (t) {
156                    context.addBundleListener(t);
157                    Bundle[] bundles = context.getBundles();
158                    if (bundles != null) {
159                        int length = bundles.length;
160                        for (int i = 0; i < length; i++) {
161                            int state = bundles[i].getState();
162                            if ((state & mask) == 0) {
163                                /* null out bundles whose states are not interesting */
164                                bundles[i] = null;
165                            }
166                        }
167                        /* set tracked with the initial bundles */
168                        t.setInitial(bundles);
169                    }
170                }
171                tracked = t;
172            }
173            /* Call tracked outside of synchronized region */
174            t.trackInitial(); /* process the initial references */
175        }
176    
177        /**
178         * Close this <code>BundleTracker</code>.
179         * <p>
180         * This method should be called when this <code>BundleTracker</code> should
181         * end the tracking of bundles.
182         * <p>
183         * This implementation calls {@link #getBundles()} to get the list of
184         * tracked bundles to remove.
185         */
186        public void close() {
187            final Bundle[] bundles;
188            final Tracked outgoing;
189            synchronized (this) {
190                outgoing = tracked;
191                if (outgoing == null) {
192                    return;
193                }
194                if (DEBUG) {
195                    System.out.println("BundleTracker.close");
196                }
197                outgoing.close();
198                bundles = getBundles();
199                tracked = null;
200                try {
201                    context.removeBundleListener(outgoing);
202                } catch (IllegalStateException e) {
203                    /* In case the context was stopped. */
204                }
205            }
206            if (bundles != null) {
207                for (Bundle bundle : bundles) {
208                    outgoing.untrack(bundle, null);
209                }
210            }
211        }
212    
213        /**
214         * Default implementation of the
215         * <code>BundleTrackerCustomizer.addingBundle</code> method.
216         * <p>
217         * This method is only called when this <code>BundleTracker</code> has been
218         * constructed with a <code>null BundleTrackerCustomizer</code> argument.
219         * <p>
220         * This implementation simply returns the specified <code>Bundle</code>.
221         * <p>
222         * This method can be overridden in a subclass to customize the object to be
223         * tracked for the bundle being added.
224         * 
225         * @param bundle The <code>Bundle</code> being added to this
226         *            <code>BundleTracker</code> object.
227         * @param event The bundle event which caused this customizer method to be
228         *            called or <code>null</code> if there is no bundle event
229         *            associated with the call to this method.
230         * @return The specified bundle.
231         * @see BundleTrackerCustomizer#addingBundle(Bundle, BundleEvent)
232         */
233        public Object addingBundle(Bundle bundle, BundleEvent event) {
234            return bundle;
235        }
236    
237        /**
238         * Default implementation of the
239         * <code>BundleTrackerCustomizer.modifiedBundle</code> method.
240         * <p>
241         * This method is only called when this <code>BundleTracker</code> has been
242         * constructed with a <code>null BundleTrackerCustomizer</code> argument.
243         * <p>
244         * This implementation does nothing.
245         * 
246         * @param bundle The <code>Bundle</code> whose state has been modified.
247         * @param event The bundle event which caused this customizer method to be
248         *            called or <code>null</code> if there is no bundle event
249         *            associated with the call to this method.
250         * @param object The customized object for the specified Bundle.
251         * @see BundleTrackerCustomizer#modifiedBundle(Bundle, BundleEvent, Object)
252         */
253        public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
254            /* do nothing */
255        }
256    
257        /**
258         * Default implementation of the
259         * <code>BundleTrackerCustomizer.removedBundle</code> method.
260         * <p>
261         * This method is only called when this <code>BundleTracker</code> has been
262         * constructed with a <code>null BundleTrackerCustomizer</code> argument.
263         * <p>
264         * This implementation does nothing.
265         * 
266         * @param bundle The <code>Bundle</code> being removed.
267         * @param event The bundle event which caused this customizer method to be
268         *            called or <code>null</code> if there is no bundle event
269         *            associated with the call to this method.
270         * @param object The customized object for the specified bundle.
271         * @see BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)
272         */
273        public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
274            /* do nothing */
275        }
276    
277        /**
278         * Return an array of <code>Bundle</code>s for all bundles being tracked by
279         * this <code>BundleTracker</code>.
280         * 
281         * @return An array of <code>Bundle</code>s or <code>null</code> if no
282         *         bundles are being tracked.
283         */
284        public Bundle[] getBundles() {
285            final Tracked t = tracked();
286            if (t == null) { /* if BundleTracker is not open */
287                return null;
288            }
289            synchronized (t) {
290                int length = t.size();
291                if (length == 0) {
292                    return null;
293                }
294                return (Bundle[])t.getTracked(new Bundle[length]);
295            }
296        }
297    
298        /**
299         * Returns the customized object for the specified <code>Bundle</code> if
300         * the specified bundle is being tracked by this <code>BundleTracker</code>.
301         * 
302         * @param bundle The <code>Bundle</code> being tracked.
303         * @return The customized object for the specified <code>Bundle</code> or
304         *         <code>null</code> if the specified <code>Bundle</code> is not
305         *         being tracked.
306         */
307        public Object getObject(Bundle bundle) {
308            final Tracked t = tracked();
309            if (t == null) { /* if BundleTracker is not open */
310                return null;
311            }
312            synchronized (t) {
313                return t.getCustomizedObject(bundle);
314            }
315        }
316    
317        /**
318         * Remove a bundle from this <code>BundleTracker</code>. The specified
319         * bundle will be removed from this <code>BundleTracker</code> . If the
320         * specified bundle was being tracked then the
321         * <code>BundleTrackerCustomizer.removedBundle</code> method will be called
322         * for that bundle.
323         * 
324         * @param bundle The <code>Bundle</code> to be removed.
325         */
326        public void remove(Bundle bundle) {
327            final Tracked t = tracked();
328            if (t == null) { /* if BundleTracker is not open */
329                return;
330            }
331            t.untrack(bundle, null);
332        }
333    
334        /**
335         * Return the number of bundles being tracked by this
336         * <code>BundleTracker</code>.
337         * 
338         * @return The number of bundles being tracked.
339         */
340        public int size() {
341            final Tracked t = tracked();
342            if (t == null) { /* if BundleTracker is not open */
343                return 0;
344            }
345            synchronized (t) {
346                return t.size();
347            }
348        }
349    
350        /**
351         * Returns the tracking count for this <code>BundleTracker</code>. The
352         * tracking count is initialized to 0 when this <code>BundleTracker</code>
353         * is opened. Every time a bundle is added, modified or removed from this
354         * <code>BundleTracker</code> the tracking count is incremented.
355         * <p>
356         * The tracking count can be used to determine if this
357         * <code>BundleTracker</code> has added, modified or removed a bundle by
358         * comparing a tracking count value previously collected with the current
359         * tracking count value. If the value has not changed, then no bundle has
360         * been added, modified or removed from this <code>BundleTracker</code>
361         * since the previous tracking count was collected.
362         * 
363         * @return The tracking count for this <code>BundleTracker</code> or -1 if
364         *         this <code>BundleTracker</code> is not open.
365         */
366        public int getTrackingCount() {
367            final Tracked t = tracked();
368            if (t == null) { /* if BundleTracker is not open */
369                return -1;
370            }
371            synchronized (t) {
372                return t.getTrackingCount();
373            }
374        }
375    
376        /**
377         * Inner class which subclasses AbstractTracked. This class is the
378         * <code>SynchronousBundleListener</code> object for the tracker.
379         * 
380         * @ThreadSafe
381         * @since 1.4
382         */
383        class Tracked extends AbstractTracked implements SynchronousBundleListener {
384    
385            /**
386             * <code>BundleListener</code> method for the <code>BundleTracker</code>
387             * class. This method must NOT be synchronized to avoid deadlock
388             * potential.
389             * 
390             * @param event <code>BundleEvent</code> object from the framework.
391             */
392            public void bundleChanged(final BundleEvent event) {
393                /*
394                 * Check if we had a delayed call (which could happen when we
395                 * close).
396                 */
397                if (closed) {
398                    return;
399                }
400                final Bundle bundle = event.getBundle();
401                final int state = bundle.getState();
402                if (DEBUG) {
403                    System.out.println("BundleTracker.Tracked.bundleChanged[" + state + "]: " + bundle);
404                }
405    
406                if ((state & mask) != 0) {
407                    track(bundle, event);
408                    /*
409                     * If the customizer throws an unchecked exception, it is safe
410                     * to let it propagate
411                     */
412                } else {
413                    untrack(bundle, event);
414                    /*
415                     * If the customizer throws an unchecked exception, it is safe
416                     * to let it propagate
417                     */
418                }
419            }
420    
421            /**
422             * Call the specific customizer adding method. This method must not be
423             * called while synchronized on this object.
424             * 
425             * @param item Item to be tracked.
426             * @param related Action related object.
427             * @return Customized object for the tracked item or <code>null</code>
428             *         if the item is not to be tracked.
429             */
430            Object customizerAdding(final Object item, final Object related) {
431                return customizer.addingBundle((Bundle)item, (BundleEvent)related);
432            }
433    
434            /**
435             * Call the specific customizer modified method. This method must not be
436             * called while synchronized on this object.
437             * 
438             * @param item Tracked item.
439             * @param related Action related object.
440             * @param object Customized object for the tracked item.
441             */
442            void customizerModified(final Object item, final Object related, final Object object) {
443                customizer.modifiedBundle((Bundle)item, (BundleEvent)related, object);
444            }
445    
446            /**
447             * Call the specific customizer removed method. This method must not be
448             * called while synchronized on this object.
449             * 
450             * @param item Tracked item.
451             * @param related Action related object.
452             * @param object Customized object for the tracked item.
453             */
454            void customizerRemoved(final Object item, final Object related, final Object object) {
455                customizer.removedBundle((Bundle)item, (BundleEvent)related, object);
456            }
457        }
458    }