View Javadoc

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