| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // 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; |
| |
| /** |
| * PackageAdminServiceTracker |
| * <p> |
| * When the PackageAdmin service is activated we can look for the fragments |
| * attached to this bundle and do a fake "activate" on them. |
| * <p> |
| * See particularly the jetty-osgi-boot-jsp fragment bundle that uses this |
| * facility. |
| */ |
| 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 the bundle |
| * @return the bundle fragment list |
| */ |
| 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 the bundle |
| * @return the bundle fragment and required list |
| */ |
| 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 the bundle |
| * @param admin the admin package |
| * @param deps The map of fragment and required bundles associated to the value of the |
| * jetty-web attribute. |
| * @param onlyReexport true to collect resources and web-fragments |
| * transitively if and only if the directive visibility is |
| * reexport. |
| */ |
| 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 bundle the bundle |
| * @param admin the admin package |
| * @param deps The map of required bundles associated to the value of the |
| * jetty-web attribute. |
| * @param onlyReexport true to collect resources and web-fragments |
| * transitively if and only if the directive visibility is |
| * reexport. |
| */ |
| 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 '\''; |
| } |
| |
| } |
| |
| } |
| |