View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Intalio, Inc.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  package org.eclipse.jetty.osgi.boot.utils.internal;
14  
15  import java.util.ArrayList;
16  import java.util.LinkedHashMap;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.StringTokenizer;
20  
21  import org.osgi.framework.Bundle;
22  import org.osgi.framework.BundleActivator;
23  import org.osgi.framework.BundleContext;
24  import org.osgi.framework.InvalidSyntaxException;
25  import org.osgi.framework.ServiceEvent;
26  import org.osgi.framework.ServiceListener;
27  import org.osgi.framework.ServiceReference;
28  import org.osgi.service.packageadmin.PackageAdmin;
29  import org.osgi.service.startlevel.StartLevel;
30  
31  /**
32   * When the PackageAdmin service is activated we can look for the fragments
33   * attached to this bundle and "activate" them.
34   */
35  public class PackageAdminServiceTracker implements ServiceListener
36  {
37      private BundleContext _context;
38  
39      private List<BundleActivator> _activatedFragments = new ArrayList<BundleActivator>();
40  
41      private boolean _fragmentsWereActivated = false;
42  
43      // Use the deprecated StartLevel to stay compatible with older versions of
44      // OSGi.
45      private StartLevel _startLevel;
46  
47      private int _maxStartLevel = 6;
48  
49      public static PackageAdminServiceTracker INSTANCE = null;
50  
51      public PackageAdminServiceTracker(BundleContext context)
52      {
53          INSTANCE = this;
54          _context = context;
55          if (!setup())
56          {
57              try
58              {
59                  _context.addServiceListener(this, "(objectclass=" + PackageAdmin.class.getName() + ")");
60              }
61              catch (InvalidSyntaxException e)
62              {
63                  e.printStackTrace(); // won't happen
64              }
65          }
66      }
67  
68      /**
69       * @return true if the fragments were activated by this method.
70       */
71      private boolean setup()
72      {
73          ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
74          _fragmentsWereActivated = sr != null;
75          if (sr != null) invokeFragmentActivators(sr);
76  
77          sr = _context.getServiceReference(StartLevel.class.getName());
78          if (sr != null)
79          {
80              _startLevel = (StartLevel) _context.getService(sr);
81              try
82              {
83                  _maxStartLevel = Integer.parseInt(System.getProperty("osgi.startLevel", "6"));
84              }
85              catch (Exception e)
86              {
87                  // nevermind default on the usual.
88                  _maxStartLevel = 6;
89              }
90          }
91          return _fragmentsWereActivated;
92      }
93  
94      /**
95       * Invokes the optional BundleActivator in each fragment. By convention the
96       * bundle activator for a fragment must be in the package that is defined by
97       * the symbolic name of the fragment and the name of the class must be
98       * 'FragmentActivator'.
99       * 
100      * @param event The <code>ServiceEvent</code> object.
101      */
102     public void serviceChanged(ServiceEvent event)
103     {
104         if (event.getType() == ServiceEvent.REGISTERED)
105         {
106             invokeFragmentActivators(event.getServiceReference());
107         }
108     }
109 
110     /**
111      * Helper to access the PackageAdmin and return the fragments hosted by a
112      * bundle. when we drop the support for the older versions of OSGi, we will
113      * stop using the PackageAdmin service.
114      * 
115      * @param bundle
116      * @return
117      */
118     public Bundle[] getFragments(Bundle bundle)
119     {
120         ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
121         if (sr == null)
122         {// we should never be here really.
123             return null;
124         }
125         PackageAdmin admin = (PackageAdmin) _context.getService(sr);
126         return admin.getFragments(bundle);
127     }
128 
129     /**
130      * Returns the fragments and the required-bundles of a bundle. Recursively
131      * collect the required-bundles and fragment when the directive
132      * visibility:=reexport is added to a required-bundle.
133      * 
134      * @param bundle
135      * @param webFragOrAnnotationOrResources
136      * @return
137      */
138     public Bundle[] getFragmentsAndRequiredBundles(Bundle bundle)
139     {
140         ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
141         if (sr == null)
142         {// we should never be here really.
143             return null;
144         }
145         PackageAdmin admin = (PackageAdmin) _context.getService(sr);
146         LinkedHashMap<String, Bundle> deps = new LinkedHashMap<String, Bundle>();
147         collectFragmentsAndRequiredBundles(bundle, admin, deps, false);
148         return deps.values().toArray(new Bundle[deps.size()]);
149     }
150 
151     /**
152      * Returns the fragments and the required-bundles. Collects them
153      * transitively when the directive 'visibility:=reexport' is added to a
154      * required-bundle.
155      * 
156      * @param bundle
157      * @param webFragOrAnnotationOrResources
158      * @return
159      */
160     protected void collectFragmentsAndRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String, Bundle> deps, boolean onlyReexport)
161     {
162         Bundle[] fragments = admin.getFragments(bundle);
163         if (fragments != null)
164         {
165             // Also add the bundles required by the fragments.
166             // this way we can inject onto an existing web-bundle a set of
167             // bundles that extend it
168             for (Bundle f : fragments)
169             {
170                 if (!deps.keySet().contains(f.getSymbolicName()))
171                 {
172                     deps.put(f.getSymbolicName(), f);
173                     collectRequiredBundles(f, admin, deps, onlyReexport);
174                 }
175             }
176         }
177         collectRequiredBundles(bundle, admin, deps, onlyReexport);
178     }
179 
180     /**
181      * A simplistic but good enough parser for the Require-Bundle header. Parses
182      * the version range attribute and the visibility directive.
183      * 
184      * @param onlyReexport true to collect resources and web-fragments
185      *            transitively if and only if the directive visibility is
186      *            reexport.
187      * @param bundle
188      * @return The map of required bundles associated to the value of the
189      *         jetty-web attribute.
190      */
191     protected void collectRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String, Bundle> deps, boolean onlyReexport)
192     {
193         String requiredBundleHeader = (String) bundle.getHeaders().get("Require-Bundle");
194         if (requiredBundleHeader == null) { return; }
195         StringTokenizer tokenizer = new ManifestTokenizer(requiredBundleHeader);
196         while (tokenizer.hasMoreTokens())
197         {
198             String tok = tokenizer.nextToken().trim();
199             StringTokenizer tokenizer2 = new StringTokenizer(tok, ";");
200             String symbolicName = tokenizer2.nextToken().trim();
201             if (deps.keySet().contains(symbolicName))
202             {
203                 // was already added. 2 dependencies pointing at the same
204                 // bundle.
205                 continue;
206             }
207             String versionRange = null;
208             boolean reexport = false;
209             while (tokenizer2.hasMoreTokens())
210             {
211                 String next = tokenizer2.nextToken().trim();
212                 if (next.startsWith("bundle-version="))
213                 {
214                     if (next.startsWith("bundle-version=\"") || next.startsWith("bundle-version='"))
215                     {
216                         versionRange = next.substring("bundle-version=\"".length(), next.length() - 1);
217                     }
218                     else
219                     {
220                         versionRange = next.substring("bundle-version=".length());
221                     }
222                 }
223                 else if (next.equals("visibility:=reexport"))
224                 {
225                     reexport = true;
226                 }
227             }
228             if (!reexport && onlyReexport) { return; }
229             Bundle[] reqBundles = admin.getBundles(symbolicName, versionRange);
230             if (reqBundles != null && reqBundles.length != 0)
231             {
232                 Bundle reqBundle = null;
233                 for (Bundle b : reqBundles)
234                 {
235                     if (b.getState() == Bundle.ACTIVE || b.getState() == Bundle.STARTING)
236                     {
237                         reqBundle = b;
238                         break;
239                     }
240                 }
241                 if (reqBundle == null)
242                 {
243                     // strange? in OSGi with Require-Bundle,
244                     // the dependent bundle is supposed to be active already
245                     reqBundle = reqBundles[0];
246                 }
247                 deps.put(reqBundle.getSymbolicName(), reqBundle);
248                 collectFragmentsAndRequiredBundles(reqBundle, admin, deps, true);
249             }
250         }
251     }
252 
253     private void invokeFragmentActivators(ServiceReference sr)
254     {
255         PackageAdmin admin = (PackageAdmin) _context.getService(sr);
256         Bundle[] fragments = admin.getFragments(_context.getBundle());
257         if (fragments == null) { return; }
258         for (Bundle frag : fragments)
259         {
260             // find a convention to look for a class inside the fragment.
261             try
262             {
263                 String fragmentActivator = frag.getSymbolicName() + ".FragmentActivator";
264                 Class<?> c = Class.forName(fragmentActivator);
265                 if (c != null)
266                 {
267                     BundleActivator bActivator = (BundleActivator) c.newInstance();
268                     bActivator.start(_context);
269                     _activatedFragments.add(bActivator);
270                 }
271             }
272             catch (NullPointerException e)
273             {
274                 // e.printStackTrace();
275             }
276             catch (InstantiationException e)
277             {
278                 // e.printStackTrace();
279             }
280             catch (IllegalAccessException e)
281             {
282                 // e.printStackTrace();
283             }
284             catch (ClassNotFoundException e)
285             {
286                 // e.printStackTrace();
287             }
288             catch (Exception e)
289             {
290                 e.printStackTrace();
291             }
292         }
293     }
294 
295     public void stop()
296     {
297         INSTANCE = null;
298         for (BundleActivator fragAct : _activatedFragments)
299         {
300             try
301             {
302                 fragAct.stop(_context);
303             }
304             catch (Exception e)
305             {
306                 e.printStackTrace();
307             }
308         }
309     }
310 
311     /**
312      * @return true if the framework has completed all the start levels.
313      */
314     public boolean frameworkHasCompletedAutostarts()
315     {
316         return _startLevel == null ? true : _startLevel.getStartLevel() >= _maxStartLevel;
317     }
318 
319     private static class ManifestTokenizer extends StringTokenizer
320     {
321 
322         public ManifestTokenizer(String header)
323         {
324             super(header, ",");
325         }
326 
327         @Override
328         public String nextToken()
329         {
330             String token = super.nextToken();
331 
332             while (hasOpenQuote(token) && hasMoreTokens())
333             {
334                 token += "," + super.nextToken();
335             }
336             return token;
337         }
338 
339         private boolean hasOpenQuote(String token)
340         {
341             int i = -1;
342             do
343             {
344                 int quote = getQuote(token, i + 1);
345                 if (quote < 0) { return false; }
346 
347                 i = token.indexOf(quote, i + 1);
348                 i = token.indexOf(quote, i + 1);
349             }
350             while (i >= 0);
351             return true;
352         }
353 
354         private int getQuote(String token, int offset)
355         {
356             int i = token.indexOf('"', offset);
357             int j = token.indexOf('\'', offset);
358             if (i < 0)
359             {
360                 if (j < 0)
361                 {
362                     return -1;
363                 }
364                 else
365                 {
366                     return '\'';
367                 }
368             }
369             if (j < 0) { return '"'; }
370             if (i < j) { return '"'; }
371             return '\'';
372         }
373 
374     }
375 
376 }
377