| /******************************************************************************* |
| * This file is part of the Virgo Web Server. |
| * |
| * Copyright (c) 2010 VMware Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SpringSource, a division of VMware - initial API and implementation and/or initial documentation |
| *******************************************************************************/ |
| |
| package org.eclipse.virgo.kernel.userregionfactory; |
| |
| import static org.eclipse.virgo.kernel.osgi.framework.ServiceUtils.getPotentiallyDelayedService; |
| |
| import java.net.URI; |
| import java.util.*; |
| |
| import org.eclipse.equinox.region.Region; |
| import org.eclipse.equinox.region.RegionDigraph; |
| import org.eclipse.equinox.region.RegionFilter; |
| import org.eclipse.equinox.region.RegionFilterBuilder; |
| import org.eclipse.virgo.nano.core.Shutdown; |
| import org.eclipse.virgo.medic.dump.DumpGenerator; |
| import org.eclipse.virgo.medic.eventlog.EventLogger; |
| import org.eclipse.virgo.util.common.PropertyPlaceholderResolver; |
| import org.eclipse.virgo.util.parser.launcher.ArgumentParser; |
| import org.eclipse.virgo.util.parser.launcher.BundleEntry; |
| import org.eclipse.virgo.util.osgi.ServiceRegistrationTracker; |
| import org.eclipse.virgo.util.osgi.manifest.BundleManifest; |
| import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory; |
| import org.eclipse.virgo.util.osgi.manifest.ImportedPackage; |
| import org.eclipse.virgo.util.osgi.manifest.RequireBundle; |
| import org.eclipse.virgo.util.osgi.manifest.RequiredBundle; |
| import org.eclipse.virgo.util.osgi.manifest.VersionRange; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.startlevel.FrameworkStartLevel; |
| import org.osgi.service.cm.Configuration; |
| import org.osgi.service.cm.ConfigurationAdmin; |
| import org.osgi.service.component.ComponentContext; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventAdmin; |
| |
| /** |
| * {@link Activator} initialises the user region factory bundle. |
| * <p /> |
| * |
| * <strong>Concurrent Semantics</strong><br /> |
| * |
| * Not thread safe. |
| * |
| */ |
| public final class Activator { |
| |
| private static final int DEFAULT_BUNDLE_START_LEVEL = 4; |
| |
| private static final String KERNEL_REGION_NAME = "org.eclipse.equinox.region.kernel"; |
| |
| private static final String CLASS_LIST_SEPARATOR = ","; |
| |
| private static final String USER_REGION_CONFIGURATION_PID = "org.eclipse.virgo.kernel.userregion"; |
| |
| private static final String USER_REGION_BASE_BUNDLES_PROPERTY = "baseBundles"; |
| |
| private static final String USER_REGION_PACKAGE_IMPORTS_PROPERTY = "packageImports"; |
| |
| private static final String USER_REGION_SERVICE_IMPORTS_PROPERTY = "serviceImports"; |
| |
| private static final String USER_REGION_BUNDLE_IMPORTS_PROPERTY = "bundleImports"; |
| |
| private static final String USER_REGION_SERVICE_EXPORTS_PROPERTY = "serviceExports"; |
| |
| private static final String USER_REGION_BUNDLE_CONTEXT_SERVICE_PROPERTY = "org.eclipse.virgo.kernel.regionContext"; |
| |
| private static final String REGION_USER = "org.eclipse.virgo.region.user"; |
| |
| private static final String EVENT_REGION_STARTING = "org/eclipse/virgo/kernel/region/STARTING"; |
| |
| private static final String EVENT_PROPERTY_REGION_BUNDLECONTEXT = "region.bundleContext"; |
| |
| private static final String WILDCARD = "*"; |
| |
| private EventAdmin eventAdmin; |
| |
| private String regionBundles; |
| |
| private String regionPackageImports; |
| |
| private String regionServiceImports; |
| |
| private String regionBundleImports; |
| |
| private String regionServiceExports; |
| |
| private Properties regionVariables = new Properties(); |
| |
| private BundleContext bundleContext; |
| |
| private final ArgumentParser parser = new ArgumentParser(); |
| |
| private final ServiceRegistrationTracker tracker = new ServiceRegistrationTracker(); |
| |
| private DumpGenerator dumpGenerator; |
| |
| public void activate(ComponentContext componentContext) throws Exception { |
| this.bundleContext = componentContext.getBundleContext(); |
| |
| FrameworkStartLevel frameworkStartLevel = componentContext.getBundleContext().getBundle(0).adapt(FrameworkStartLevel.class); |
| frameworkStartLevel.setInitialBundleStartLevel(DEFAULT_BUNDLE_START_LEVEL); |
| |
| this.dumpGenerator = getPotentiallyDelayedService(bundleContext, DumpGenerator.class); |
| RegionDigraph regionDigraph = getPotentiallyDelayedService(bundleContext, RegionDigraph.class); |
| this.eventAdmin = getPotentiallyDelayedService(bundleContext, EventAdmin.class); |
| ConfigurationAdmin configAdmin = getPotentiallyDelayedService(bundleContext, ConfigurationAdmin.class); |
| EventLogger eventLogger = getPotentiallyDelayedService(bundleContext, EventLogger.class); |
| Shutdown shutdown = getPotentiallyDelayedService(bundleContext, Shutdown.class); |
| getRegionConfiguration(configAdmin, eventLogger, shutdown); |
| |
| createUserRegion(regionDigraph, eventLogger); |
| } |
| |
| private void getRegionConfiguration(ConfigurationAdmin configAdmin, EventLogger eventLogger, Shutdown shutdown) { |
| try { |
| Configuration config = configAdmin.getConfiguration(USER_REGION_CONFIGURATION_PID, null); |
| |
| Dictionary<String, Object> properties = config.getProperties(); |
| |
| if (properties != null) { |
| this.regionBundles = properties.get(USER_REGION_BASE_BUNDLES_PROPERTY).toString(); |
| this.regionPackageImports = properties.get(USER_REGION_PACKAGE_IMPORTS_PROPERTY).toString(); |
| this.regionServiceImports = properties.get(USER_REGION_SERVICE_IMPORTS_PROPERTY).toString(); |
| this.regionBundleImports = properties.get(USER_REGION_BUNDLE_IMPORTS_PROPERTY).toString(); |
| this.regionServiceExports = properties.get(USER_REGION_SERVICE_EXPORTS_PROPERTY).toString(); |
| |
| Collections.list(properties.keys()).stream() |
| .filter(key -> !key.equals(USER_REGION_BASE_BUNDLES_PROPERTY) |
| && !key.equals(USER_REGION_PACKAGE_IMPORTS_PROPERTY) |
| && !key.equals(USER_REGION_SERVICE_IMPORTS_PROPERTY) |
| && !key.equals(USER_REGION_BUNDLE_IMPORTS_PROPERTY) |
| && !key.equals(USER_REGION_SERVICE_EXPORTS_PROPERTY)) |
| .forEach(key -> this.regionVariables.put(key, properties.get(key))); |
| } else { |
| eventLogger.log(UserRegionFactoryLogEvents.USER_REGION_CONFIGURATION_UNAVAILABLE); |
| shutdown.immediateShutdown(); |
| } |
| } catch (Exception e) { |
| eventLogger.log(UserRegionFactoryLogEvents.USER_REGION_CONFIGURATION_UNAVAILABLE, e); |
| shutdown.immediateShutdown(); |
| } |
| } |
| |
| private void createUserRegion(RegionDigraph regionDigraph, EventLogger eventLogger) throws BundleException, InvalidSyntaxException { |
| |
| BundleContext systemBundleContext = getSystemBundleContext(); |
| Bundle userRegionFactoryBundle = this.bundleContext.getBundle(); |
| |
| Region kernelRegion = getKernelRegion(regionDigraph); |
| kernelRegion.removeBundle(userRegionFactoryBundle); |
| |
| Region userRegion = regionDigraph.createRegion(REGION_USER); |
| userRegion.addBundle(userRegionFactoryBundle); |
| |
| RegionFilter kernelFilter = createKernelFilter(regionDigraph, systemBundleContext, eventLogger); |
| userRegion.connectRegion(kernelRegion, kernelFilter); |
| |
| RegionFilter userRegionFilter = createUserRegionFilter(regionDigraph); |
| kernelRegion.connectRegion(userRegion, userRegionFilter); |
| |
| notifyUserRegionStarting(this.bundleContext); |
| |
| initialiseUserRegionBundles(userRegion); |
| |
| registerRegionService(userRegion); |
| publishUserRegionBundleContext(this.bundleContext); |
| } |
| |
| private RegionFilter createUserRegionFilter(RegionDigraph digraph) throws InvalidSyntaxException { |
| Collection<String> serviceFilters = classesToFilter(this.regionServiceExports); |
| RegionFilterBuilder builder = digraph.createRegionFilterBuilder(); |
| for (String filter : serviceFilters) { |
| builder.allow(RegionFilter.VISIBLE_SERVICE_NAMESPACE, filter); |
| } |
| return builder.build(); |
| } |
| |
| private Region getKernelRegion(RegionDigraph regionDigraph) { |
| return regionDigraph.getRegion(KERNEL_REGION_NAME); |
| } |
| |
| private RegionFilter createKernelFilter(RegionDigraph digraph, BundleContext systemBundleContext, EventLogger eventLogger) |
| throws InvalidSyntaxException { |
| RegionFilterBuilder builder = digraph.createRegionFilterBuilder(); |
| Collection<String> allowedBundles = allowImportedBundles(eventLogger); |
| for (String filter : allowedBundles) { |
| builder.allow(RegionFilter.VISIBLE_BUNDLE_NAMESPACE, filter); |
| } |
| Collection<String> allowedPackages = createUserRegionPackageImportPolicy(systemBundleContext, eventLogger); |
| for (String filter : allowedPackages) { |
| builder.allow(RegionFilter.VISIBLE_PACKAGE_NAMESPACE, filter); |
| } |
| Collection<String> allowedServices = classesToFilter(this.regionServiceImports); |
| for (String filter : allowedServices) { |
| builder.allow(RegionFilter.VISIBLE_SERVICE_NAMESPACE, filter); |
| } |
| return builder.build(); |
| } |
| |
| private Collection<String> allowImportedBundles(EventLogger eventLogger) { |
| String userRegionBundleImports = this.regionBundleImports != null ? this.regionBundleImports |
| : this.bundleContext.getProperty(USER_REGION_BUNDLE_IMPORTS_PROPERTY); |
| |
| RequireBundle bundleImportsAsRequireBundle = representBundleImportsAsRequireBundle(userRegionBundleImports, eventLogger); |
| return importBundleToFilter(bundleImportsAsRequireBundle.getRequiredBundles()); |
| } |
| |
| private RequireBundle representBundleImportsAsRequireBundle(String userRegionBundleImportsProperty, EventLogger eventLogger) { |
| Dictionary<String, String> headers = new Hashtable<>(); |
| headers.put("Require-Bundle", userRegionBundleImportsProperty); |
| BundleManifest manifest = BundleManifestFactory.createBundleManifest(headers, new UserRegionFactoryParserLogger(eventLogger)); |
| return manifest.getRequireBundle(); |
| } |
| |
| private static Collection<String> importBundleToFilter(List<RequiredBundle> importedBundles) { |
| if (importedBundles == null || importedBundles.isEmpty()) |
| return Collections.emptyList(); |
| Collection<String> result = new ArrayList<>(importedBundles.size()); |
| for (RequiredBundle importedBundle : importedBundles) { |
| StringBuilder f = new StringBuilder(); |
| f.append("(&(").append(RegionFilter.VISIBLE_BUNDLE_NAMESPACE).append('=').append(importedBundle.getBundleSymbolicName()).append(')'); |
| addRange(Constants.BUNDLE_VERSION_ATTRIBUTE, importedBundle.getBundleVersion(), f); |
| f.append(')'); |
| result.add(f.toString()); |
| } |
| return result; |
| } |
| |
| private static Collection<String> importPackageToFilter(String importList) { |
| if (importList == null || importList.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| if (importList.contains(WILDCARD)) { |
| throw new IllegalArgumentException("Wildcards not supported in region imports: '" + importList + "'"); |
| } |
| BundleManifest manifest = BundleManifestFactory.createBundleManifest(); |
| manifest.setHeader("Import-Package", importList); |
| List<ImportedPackage> list = manifest.getImportPackage().getImportedPackages(); |
| if (list.isEmpty()) |
| return Collections.emptyList(); |
| Collection<String> filters = new ArrayList<>(list.size()); |
| for (ImportedPackage importedPackage : list) { |
| StringBuilder f = new StringBuilder(); |
| f.append("(&(").append(RegionFilter.VISIBLE_PACKAGE_NAMESPACE).append('=').append(importedPackage.getPackageName()).append(')'); |
| Map<String, String> attrs = importedPackage.getAttributes(); |
| for (Map.Entry<String, String> attr : attrs.entrySet()) { |
| if (Constants.VERSION_ATTRIBUTE.equals(attr.getKey()) || Constants.BUNDLE_VERSION_ATTRIBUTE.equals(attr.getKey())) { |
| addRange(attr.getKey(), new VersionRange(attr.getValue()), f); |
| } else { |
| f.append('(').append(attr.getKey()).append('=').append(attr.getValue()).append(')'); |
| } |
| } |
| f.append(')'); |
| filters.add(f.toString()); |
| } |
| return filters; |
| } |
| |
| private static void addRange(String key, VersionRange range, StringBuilder f) { |
| if (range.isFloorInclusive()) { |
| f.append('(').append(key).append(">=").append(range.getFloor()).append(')'); |
| } else { |
| f.append("(!(").append(key).append("<=").append(range.getFloor()).append("))"); |
| } |
| Version ceiling = range.getCeiling(); |
| if (ceiling != null) { |
| if (range.isCeilingInclusive()) { |
| f.append('(').append(key).append("<=").append(ceiling).append(')'); |
| } else { |
| f.append("(!(").append(key).append(">=").append(ceiling).append("))"); |
| } |
| } |
| } |
| |
| private static Collection<String> classesToFilter(String classList) { |
| if (classList == null) { |
| return Collections.emptyList(); |
| } |
| String[] classes = classList.split(CLASS_LIST_SEPARATOR); |
| if (classes.length == 0) { |
| return Collections.emptyList(); |
| } |
| Collection<String> result = new ArrayList<>(classes.length); |
| for (String className : classes) { |
| result.add("(objectClass=" + className + ")"); |
| } |
| return result; |
| } |
| |
| private Collection<String> createUserRegionPackageImportPolicy(BundleContext systemBundleContext, EventLogger eventLogger) { |
| String userRegionImportsProperty = this.regionPackageImports != null ? this.regionPackageImports |
| : this.bundleContext.getProperty(USER_REGION_PACKAGE_IMPORTS_PROPERTY); |
| String expandedUserRegionImportsProperty = null; |
| if (userRegionImportsProperty != null) { |
| expandedUserRegionImportsProperty = PackageImportWildcardExpander.expandPackageImportsWildcards(userRegionImportsProperty, |
| systemBundleContext, eventLogger); |
| } |
| return importPackageToFilter(expandedUserRegionImportsProperty); |
| } |
| |
| private BundleContext getSystemBundleContext() { |
| return this.bundleContext.getBundle(0L).getBundleContext(); |
| } |
| |
| private void notifyUserRegionStarting(BundleContext userRegionBundleContext) { |
| Map<String, Object> properties = new HashMap<>(); |
| properties.put(EVENT_PROPERTY_REGION_BUNDLECONTEXT, userRegionBundleContext); |
| this.eventAdmin.sendEvent(new Event(EVENT_REGION_STARTING, properties)); |
| } |
| |
| private void initialiseUserRegionBundles(Region userRegion) throws BundleException { |
| |
| String userRegionBundlesProperty = this.regionBundles != null ? this.regionBundles |
| : this.bundleContext.getProperty(USER_REGION_BASE_BUNDLES_PROPERTY); |
| |
| if (userRegionBundlesProperty != null) { |
| try { |
| userRegionBundlesProperty = new PropertyPlaceholderResolver().resolve(userRegionBundlesProperty, this.regionVariables); |
| } catch (Exception e) { |
| throw new BundleException("Failed to resolve variables in " + USER_REGION_BASE_BUNDLES_PROPERTY, e); |
| } |
| |
| List<Bundle> bundlesToStart = new ArrayList<>(); |
| for (BundleEntry entry : this.parser.parseBundleEntries(userRegionBundlesProperty)) { |
| URI uri = entry.getURI(); |
| Bundle bundle = userRegion.installBundle(uri.toString()); |
| |
| if (entry.isAutoStart()) { |
| bundlesToStart.add(bundle); |
| } |
| } |
| |
| for (Bundle bundle : bundlesToStart) { |
| try { |
| bundle.start(); |
| } catch (BundleException e) { |
| // Take state dump for diagnosis of resolution failures |
| this.dumpGenerator.generateDump("User region bundle failed to start", e); |
| throw new BundleException("Failed to start bundle " + bundle.getSymbolicName() + " " + bundle.getVersion(), e); |
| } |
| } |
| } |
| } |
| |
| private void registerRegionService(Region region) { |
| Dictionary<String, String> props = new Hashtable<>(); |
| props.put("org.eclipse.virgo.kernel.region.name", region.getName()); |
| this.tracker.track(this.bundleContext.registerService(Region.class, region, props)); |
| } |
| |
| private void publishUserRegionBundleContext(BundleContext userRegionBundleContext) { |
| Dictionary<String, String> properties = new Hashtable<>(); |
| properties.put(USER_REGION_BUNDLE_CONTEXT_SERVICE_PROPERTY, "true"); |
| this.bundleContext.registerService(BundleContext.class, userRegionBundleContext, properties); |
| } |
| |
| public void deactivate(ComponentContext context) { |
| } |
| |
| } |