// ========================================================================
// Copyright (c) 2009 Intalio, Inc.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.osgi.boot.utils.internal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
/**
* When the PackageAdmin service is activated we can look for the fragments
* attached to this bundle and "activate" them.
*/
public class PackageAdminServiceTracker implements ServiceListener
{
private BundleContext _context;
private List<BundleActivator> _activatedFragments = new ArrayList<BundleActivator>();
private boolean _fragmentsWereActivated = false;
//Use the deprecated StartLevel to stay compatible with older versions of OSGi.
private StartLevel _startLevel;
private int _maxStartLevel = 6;
public static PackageAdminServiceTracker INSTANCE = null;
public PackageAdminServiceTracker(BundleContext context)
{
INSTANCE = this;
_context = context;
if (!setup())
{
try
{
_context.addServiceListener(this,"(objectclass=" + PackageAdmin.class.getName() + ")");
}
catch (InvalidSyntaxException e)
{
e.printStackTrace(); // won't happen
}
}
}
/**
* @return true if the fragments were activated by this method.
*/
private boolean setup()
{
ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
_fragmentsWereActivated = sr != null;
if (sr != null)
invokeFragmentActivators(sr);
sr = _context.getServiceReference(StartLevel.class.getName());
if (sr != null)
{
_startLevel = (StartLevel)_context.getService(sr);
try
{
_maxStartLevel = Integer.parseInt(System.getProperty("osgi.startLevel","6"));
}
catch (Exception e)
{
//nevermind default on the usual.
_maxStartLevel = 6;
}
}
return _fragmentsWereActivated;
}
/**
* Invokes the optional BundleActivator in each fragment. By convention the
* bundle activator for a fragment must be in the package that is defined by
* the symbolic name of the fragment and the name of the class must be
* 'FragmentActivator'.
*
* @param event
* The <code>ServiceEvent</code> object.
*/
public void serviceChanged(ServiceEvent event)
{
if (event.getType() == ServiceEvent.REGISTERED)
{
invokeFragmentActivators(event.getServiceReference());
}
}
/**
* Helper to access the PackageAdmin and return the fragments hosted by a bundle.
* when we drop the support for the older versions of OSGi, we will stop using the PackageAdmin
* service.
* @param bundle
* @return
*/
public Bundle[] getFragments(Bundle bundle)
{
ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
if (sr == null)
{//we should never be here really.
return null;
}
PackageAdmin admin = (PackageAdmin)_context.getService(sr);
return admin.getFragments(bundle);
}
/**
* Returns the fragments and the required-bundles of a bundle.
* Recursively collect the required-bundles and fragment when the directive visibility:=reexport
* is added to a required-bundle.
* @param bundle
* @param webFragOrAnnotationOrResources
* @return
*/
public Bundle[] getFragmentsAndRequiredBundles(Bundle bundle)
{
ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName());
if (sr == null)
{//we should never be here really.
return null;
}
PackageAdmin admin = (PackageAdmin)_context.getService(sr);
LinkedHashMap<String,Bundle> deps = new LinkedHashMap<String,Bundle>();
collectFragmentsAndRequiredBundles(bundle, admin, deps, false);
return deps.values().toArray(new Bundle[deps.size()]);
}
/**
* Returns the fragments and the required-bundles. Collects them transitively when the directive 'visibility:=reexport'
* is added to a required-bundle.
* @param bundle
* @param webFragOrAnnotationOrResources
* @return
*/
protected void collectFragmentsAndRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String,Bundle> deps, boolean onlyReexport)
{
Bundle[] fragments = admin.getFragments(bundle);
if (fragments != null)
{
//Also add the bundles required by the fragments.
//this way we can inject onto an existing web-bundle a set of bundles that extend it
for (Bundle f : fragments)
{
if (!deps.keySet().contains(f.getSymbolicName()))
{
deps.put(f.getSymbolicName(), f);
collectRequiredBundles(f, admin, deps, onlyReexport);
}
}
}
collectRequiredBundles(bundle, admin, deps, onlyReexport);
}
/**
* A simplistic but good enough parser for the Require-Bundle header.
* Parses the version range attribute and the visibility directive.
*
* @param onlyReexport true to collect resources and web-fragments transitively if and only if the directive visibility is reexport.
* @param bundle
* @return The map of required bundles associated to the value of the jetty-web attribute.
*/
protected void collectRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String,Bundle> deps, boolean onlyReexport)
{
String requiredBundleHeader = (String)bundle.getHeaders().get("Require-Bundle");
if (requiredBundleHeader == null)
{
return;
}
StringTokenizer tokenizer = new ManifestTokenizer(requiredBundleHeader);
while (tokenizer.hasMoreTokens())
{
String tok = tokenizer.nextToken().trim();
StringTokenizer tokenizer2 = new StringTokenizer(tok, ";");
String symbolicName = tokenizer2.nextToken().trim();
if (deps.keySet().contains(symbolicName))
{
//was already added. 2 dependencies pointing at the same bundle.
continue;
}
String versionRange = null;
boolean reexport = false;
while (tokenizer2.hasMoreTokens())
{
String next = tokenizer2.nextToken().trim();
if (next.startsWith("bundle-version="))
{
if (next.startsWith("bundle-version=\"") || next.startsWith("bundle-version='"))
{
versionRange = next.substring("bundle-version=\"".length(), next.length()-1);
}
else
{
versionRange = next.substring("bundle-version=".length());
}
}
else if (next.equals("visibility:=reexport"))
{
reexport = true;
}
}
if (!reexport && onlyReexport)
{
return;
}
Bundle[] reqBundles = admin.getBundles(symbolicName, versionRange);
if (reqBundles != null && reqBundles.length != 0)
{
Bundle reqBundle = null;
for (Bundle b : reqBundles)
{
if (b.getState() == Bundle.ACTIVE || b.getState() == Bundle.STARTING)
{
reqBundle = b;
break;
}
}
if (reqBundle == null)
{
//strange? in OSGi with Require-Bundle,
//the dependent bundle is supposed to be active already
reqBundle = reqBundles[0];
}
deps.put(reqBundle.getSymbolicName(),reqBundle);
collectFragmentsAndRequiredBundles(reqBundle, admin, deps, true);
}
}
}
private void invokeFragmentActivators(ServiceReference sr)
{
PackageAdmin admin = (PackageAdmin)_context.getService(sr);
Bundle[] fragments = admin.getFragments(_context.getBundle());
if (fragments == null)
{
return;
}
for (Bundle frag : fragments)
{
// find a convention to look for a class inside the fragment.
try
{
String fragmentActivator = frag.getSymbolicName() + ".FragmentActivator";
Class<?> c = Class.forName(fragmentActivator);
if (c != null)
{
BundleActivator bActivator = (BundleActivator)c.newInstance();
bActivator.start(_context);
_activatedFragments.add(bActivator);
}
}
catch (NullPointerException e)
{
// e.printStackTrace();
}
catch (InstantiationException e)
{
// e.printStackTrace();
}
catch (IllegalAccessException e)
{
// e.printStackTrace();
}
catch (ClassNotFoundException e)
{
// e.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public void stop()
{
INSTANCE = null;
for (BundleActivator fragAct : _activatedFragments)
{
try
{
fragAct.stop(_context);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* @return true if the framework has completed all the start levels.
*/
public boolean frameworkHasCompletedAutostarts()
{
return _startLevel == null ? true : _startLevel.getStartLevel() >= _maxStartLevel;
}
private static class ManifestTokenizer extends StringTokenizer {
public ManifestTokenizer(String header) {
super(header, ",");
}
@Override
public String nextToken() {
String token = super.nextToken();
while (hasOpenQuote(token) && hasMoreTokens()) {
token += "," + super.nextToken();
}
return token;
}
private boolean hasOpenQuote(String token) {
int i = -1;
do {
int quote = getQuote(token, i+1);
if (quote < 0) {
return false;
}
i = token.indexOf(quote, i+1);
i = token.indexOf(quote, i+1);
} while (i >= 0);
return true;
}
private int getQuote(String token, int offset) {
int i = token.indexOf('"', offset);
int j = token.indexOf('\'', offset);
if (i < 0) {
if (j < 0) {
return -1;
} else {
return '\'';
}
}
if (j < 0) {
return '"';
}
if (i < j) {
return '"';
}
return '\'';
}
}
}