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
034package org.apache.camel.impl.osgi.tracker;
035
036import org.osgi.framework.Bundle;
037import org.osgi.framework.BundleContext;
038import org.osgi.framework.BundleEvent;
039import 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 */
066public 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}