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