View Javadoc

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