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