/* * JBoss, Home of Professional Open Source * Copyright 2005, JBoss Inc., and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.osgi.service; import static org.jboss.as.osgi.OSGiLogger.LOGGER; import static org.jboss.as.osgi.OSGiMessages.MESSAGES; import static org.jboss.as.server.Services.JBOSS_SERVER_CONTROLLER; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.jboss.as.controller.ModelController; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.as.controller.client.helpers.standalone.ServerDeploymentHelper; import org.jboss.as.controller.client.helpers.standalone.ServerDeploymentManager; import org.jboss.as.osgi.OSGiConstants; import org.jboss.as.osgi.deployment.BundleDeploymentProcessor; import org.jboss.as.osgi.management.OperationAssociation; import org.jboss.as.server.deployment.Attachments; import org.jboss.as.server.deployment.DeploymentUnit; import org.jboss.as.server.deployment.DeploymentUtils; import org.jboss.as.server.deployment.Phase; import org.jboss.as.server.moduleservice.ServiceModuleLoader; import org.jboss.modules.ModuleIdentifier; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceController.Mode; import org.jboss.msc.service.ServiceController.State; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceRegistry; import org.jboss.msc.service.StabilityMonitor; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.jboss.osgi.deployment.deployer.Deployment; import org.jboss.osgi.framework.FrameworkMessages; import org.jboss.osgi.framework.Services; import org.jboss.osgi.framework.spi.BundleLifecycle; import org.jboss.osgi.framework.spi.BundleLifecyclePlugin; import org.jboss.osgi.framework.spi.BundleManager; import org.jboss.osgi.framework.spi.DeploymentProvider; import org.jboss.osgi.framework.spi.FutureServiceValue; import org.jboss.osgi.framework.spi.IntegrationConstants; import org.jboss.osgi.framework.spi.IntegrationServices; import org.jboss.osgi.framework.spi.LockManager; import org.jboss.osgi.framework.spi.StorageManager; import org.jboss.osgi.resolver.XBundle; import org.jboss.osgi.resolver.XBundleRevision; import org.jboss.osgi.resolver.XEnvironment; import org.jboss.osgi.resolver.XResolveContext; import org.jboss.osgi.resolver.XResolver; import org.jboss.osgi.resolver.XResource; import org.jboss.osgi.spi.AttachmentKey; import org.jboss.osgi.vfs.AbstractVFS; import org.jboss.osgi.vfs.VirtualFile; import org.jboss.vfs.VFSUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import org.osgi.framework.wiring.BundleRevisions; import org.osgi.service.resolver.ResolutionException; /** * An {@link org.jboss.osgi.framework.spi.IntegrationService} that that handles the bundle lifecycle. * * @author thomas.diesler@jboss.com * @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a> * @since 24-Nov-2010 */ public final class BundleLifecycleIntegration extends BundleLifecyclePlugin { private static final AttachmentKey<String> RUNTIME_NAME_KEY = AttachmentKey.create(String.class); private static final AttachmentKey<Boolean> BUNDLE_REFRESHING_KEY = AttachmentKey.create(Boolean.class); private static final Map<String, Deployment> deploymentMap = new HashMap<String, Deployment>(); private final InjectedValue<ModelController> injectedController = new InjectedValue<ModelController>(); private final InjectedValue<BundleManager> injectedBundleManager = new InjectedValue<BundleManager>(); private final InjectedValue<XEnvironment> injectedEnvironment = new InjectedValue<XEnvironment>(); private final InjectedValue<XResolver> injectedResolver = new InjectedValue<XResolver>(); private final InjectedValue<StorageManager> injectedStorageManager = new InjectedValue<StorageManager>(); private final InjectedValue<DeploymentProvider> injectedDeploymentManager = new InjectedValue<DeploymentProvider>(); private final InjectedValue<LockManager> injectedLockManager = new InjectedValue<LockManager>(); private ServerDeploymentManager serverDeploymentManager; private ModelControllerClient modelControllerClient; @Override protected void addServiceDependencies(ServiceBuilder<BundleLifecycle> builder) { super.addServiceDependencies(builder); builder.addDependency(JBOSS_SERVER_CONTROLLER, ModelController.class, injectedController); builder.addDependency(IntegrationServices.STORAGE_MANAGER_PLUGIN, StorageManager.class, injectedStorageManager); builder.addDependency(IntegrationServices.DEPLOYMENT_PROVIDER_PLUGIN, DeploymentProvider.class, injectedDeploymentManager); builder.addDependency(IntegrationServices.LOCK_MANAGER_PLUGIN, LockManager.class, injectedLockManager); builder.addDependency(Services.BUNDLE_MANAGER, BundleManager.class, injectedBundleManager); builder.addDependency(Services.ENVIRONMENT, XEnvironment.class, injectedEnvironment); builder.addDependency(Services.RESOLVER, XResolver.class, injectedResolver); builder.addDependency(Services.FRAMEWORK_CREATE); builder.setInitialMode(Mode.ON_DEMAND); } @Override public void start(StartContext startContext) throws StartException { super.start(startContext); ModelController modelController = injectedController.getValue(); modelControllerClient = modelController.createClient(Executors.newCachedThreadPool()); serverDeploymentManager = ServerDeploymentManager.Factory.create(modelControllerClient); } @Override public void stop(StopContext context) { try { modelControllerClient.close(); } catch (IOException ex) { // ignore } super.stop(context); } @Override protected BundleLifecycle createServiceValue(StartContext startContext) throws StartException { return new BundleLifecycleImpl(); } public static Deployment getDeployment(String runtimeName) { synchronized (deploymentMap) { return deploymentMap.get(runtimeName); } } public static Deployment removeDeployment(String runtimeName) { synchronized (deploymentMap) { return deploymentMap.remove(runtimeName); } } private void putDeployment(String runtimeName, final Deployment dep) { synchronized (deploymentMap) { deploymentMap.put(runtimeName, dep); } } public static boolean isBundleRefreshing(XBundle bundle) { return Boolean.TRUE.equals(bundle.getAttachment(BUNDLE_REFRESHING_KEY)); } class BundleLifecycleImpl implements BundleLifecycle { private final BundleManager bundleManager; private final XEnvironment environment; private final XResolver resolver; BundleLifecycleImpl() { this.bundleManager = injectedBundleManager.getValue(); this.environment = injectedEnvironment.getValue(); this.resolver = injectedResolver.getValue(); } @Override public XBundleRevision createBundleRevision(BundleContext context, Deployment deployment) throws BundleException { // Do the install directly if we have a running management op // https://issues.jboss.org/browse/AS7-5642 if (OperationAssociation.INSTANCE.getAssociation() != null) { LOGGER.warnCannotDeployBundleFromManagementOperation(deployment); return bundleManager.installBundleRevision(context, deployment, null); } // Check if a bundle with that location already exists if (!deployment.isBundleUpdate() && bundleManager.getBundleByLocation(deployment.getLocation()) != null) { return bundleManager.installBundleRevision(context, deployment, null); } LOGGER.debugf("Install deployment: %s", deployment); String runtimeName = getRuntimeName(deployment); putDeployment(runtimeName, deployment); try { InputStream input = deployment.getRoot().openStream(); try { ServerDeploymentHelper server = new ServerDeploymentHelper(serverDeploymentManager); server.deploy(runtimeName, input); } finally { VFSUtils.safeClose(input); } } catch (RuntimeException rte) { throw rte; } catch (Exception ex) { throw MESSAGES.cannotDeployBundleRevision(ex, deployment); } // For an already existing bundle (i.e. same location) the // {@link Deployment} may not have the bundle revision attached DeploymentUnit depUnit = deployment.getAttachment(BundleDeploymentProcessor.DEPLOYMENT_UNIT_KEY); XBundleRevision brev = depUnit.getAttachment(OSGiConstants.BUNDLE_REVISION_KEY); return brev; } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void resolve(XBundle bundle) throws ResolutionException { // Resolve the bundle bundleManager.resolveBundle(bundle); // In case there is no DeploymentUnit there is a possiblity // of a race between the first class load and the availability of the Module // Here we wait for the ModuleSpec service to come up before we are done resolving // https://issues.jboss.org/browse/AS7-6016 Deployment deployment = bundle.adapt(Deployment.class); DeploymentUnit depUnit = deployment.getAttachment(BundleDeploymentProcessor.DEPLOYMENT_UNIT_KEY); if (depUnit == null) { ModuleIdentifier identifier = bundle.getBundleRevision().getModuleIdentifier(); ServiceName moduleServiceName = ServiceModuleLoader.moduleServiceName(identifier); ServiceRegistry serviceRegistry = bundleManager.getServiceContainer(); ServiceController<?> controller = serviceRegistry.getRequiredService(moduleServiceName); FutureServiceValue<?> future = new FutureServiceValue(controller); try { future.get(2, TimeUnit.SECONDS); } catch (Exception ex) { throw FrameworkMessages.MESSAGES.illegalStateCannotLoadModule(ex, identifier); } } } @Override @SuppressWarnings("unchecked") public void start(XBundle bundle, int options) throws BundleException { Deployment deployment = bundle.adapt(Deployment.class); DeploymentUnit depUnit = deployment.getAttachment(BundleDeploymentProcessor.DEPLOYMENT_UNIT_KEY); // The DeploymentUnit would be null for initial capabilities // or for bundles that have been installed in nested mgmnt ops // https://issues.jboss.org/browse/AS7-5642 if (depUnit == null) { bundleManager.startBundle(bundle, options); return; } // There is no deferred phase, activate using the default List<String> deferredModules = DeploymentUtils.getDeferredModules(depUnit); if (!deferredModules.contains(depUnit.getName())) { bundleManager.startBundle(bundle, options); return; } // Get the INSTALL phase service and check whether we need to activate it ServiceController<Phase> phaseService = getDeferredPhaseService(depUnit); if (phaseService.getMode() != Mode.NEVER) { bundleManager.startBundle(bundle, options); return; } final ServiceName deploymentServiceName; if (depUnit.getParent() == null) { deploymentServiceName = depUnit.getServiceName(); } else { deploymentServiceName = depUnit.getParent().getServiceName(); } ServiceContainer serviceContainer = bundleManager.getServiceContainer(); ServiceController<DeploymentUnit> deploymentService = (ServiceController<DeploymentUnit>) serviceContainer.getRequiredService(deploymentServiceName); activateDeferredPhase(bundle, options, depUnit, phaseService, deploymentService); } @Override public void stop(XBundle bundle, int options) throws BundleException { bundleManager.stopBundle(bundle, options); } @Override public void removeRevision(XBundleRevision brev, int options) { undeployRevision(brev); // [AS7-6855] Bundle revision not removed when not in use any more if (brev.getState() != XResource.State.UNINSTALLED) { bundleManager.removeRevision(brev, options); } } private void undeployRevision(XBundleRevision brev) { ServerDeploymentHelper server = new ServerDeploymentHelper(serverDeploymentManager); try { Deployment deployment = brev.getAttachment(IntegrationConstants.DEPLOYMENT_KEY); server.undeploy(getRuntimeName(deployment)); } catch (Exception ex) { LOGGER.warnCannotUndeployBundleRevision(ex, brev); } } @Override public BundleRefreshPolicy getBundleRefreshPolicy() { return new RecreateCurrentRevisionPolicy(); } private void activateDeferredPhase(XBundle bundle, int options, DeploymentUnit depUnit, ServiceController<Phase> phaseService, ServiceController<DeploymentUnit> parentDeploymentService) throws BundleException { // If the Framework's current start level is less than this bundle's start level FrameworkStartLevel frameworkStartLevel = bundleManager.getSystemBundle().adapt(FrameworkStartLevel.class); BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class); int startlevel = bundleStartLevel.getStartLevel(); if (startlevel > frameworkStartLevel.getStartLevel()) { LOGGER.debugf("Start level [%d] not valid for: %s", startlevel, bundle); return; } LOGGER.infoActivateDeferredModulePhase(bundle); if (!bundle.isResolved()) { XResolveContext context = resolver.createResolveContext(environment, Collections.singleton(bundle.getBundleRevision()), null); try { resolver.resolveAndApply(context); } catch (ResolutionException ex) { throw new BundleException(FrameworkMessages.MESSAGES.cannotResolveBundle(bundle), BundleException.RESOLVE_ERROR, ex); } } depUnit.getAttachment(Attachments.DEFERRED_ACTIVATION_COUNT).incrementAndGet(); StabilityMonitor monitor = new StabilityMonitor(); monitor.addController(parentDeploymentService); monitor.addController(phaseService); Set<ServiceController<?>> failed = new HashSet<ServiceController<?>>(); Set<ServiceController<?>> problems = new HashSet<ServiceController<?>>(); try { phaseService.setMode(Mode.ACTIVE); monitor.awaitStability(failed, problems); } catch (final InterruptedException ex) { // ignore } finally { monitor.clear(); } // In case of failure we go back to NEVER if (failed.size() > 0 || problems.size() > 0) { List<ServiceController<?>> combined = new ArrayList<ServiceController<?>>(); combined.addAll(failed); combined.addAll(problems); // Collect the first start exception StartException startex = null; for (ServiceController<?> aux : combined) { if (aux.getStartException() != null) { startex = aux.getStartException(); break; } } // Create the BundleException that we throw later BundleException failure; if (startex != null && startex.getCause() instanceof BundleException) { failure = (BundleException) startex.getCause(); } else { failure = MESSAGES.cannotActivateDeferredModulePhase(startex, bundle); } // Deactivate the deferred phase depUnit.putAttachment(OSGiConstants.DEFERRED_ACTIVATION_FAILED, Boolean.TRUE); LOGGER.warnDeactivateDeferredModulePhase(bundle); phaseService.setMode(Mode.NEVER); // Wait for the phase service to come down try { FutureServiceValue<Phase> future = new FutureServiceValue<Phase>(phaseService, State.DOWN); future.get(30, TimeUnit.SECONDS); } catch (ExecutionException ex) { LOGGER.errorf(failure, failure.getMessage()); throw MESSAGES.cannotDeactivateDeferredModulePhase(ex, bundle); } catch (TimeoutException ex) { LOGGER.errorf(failure, failure.getMessage()); throw MESSAGES.cannotDeactivateDeferredModulePhase(ex, bundle); } // Throw the BundleException that caused the start failure throw failure; } } // Maps the bundle.location to a deployment runtime name private String getRuntimeName(Deployment deployment) { String runtimeName = deployment.getAttachment(RUNTIME_NAME_KEY); if (runtimeName == null) { runtimeName = deployment.getLocation(); try { // Strip the query off the location if it is a valid URI new URI(runtimeName); int queryIndex = runtimeName.indexOf('?'); if (queryIndex > 0) { runtimeName = runtimeName.substring(0, queryIndex); } } catch (URISyntaxException ex) { // ignore } if (deployment.isBundleUpdate()) { String suffix = ""; XBundle bundle = deployment.getAttachment(IntegrationConstants.BUNDLE_KEY); BundleRevisions brevs = bundle.adapt(BundleRevisions.class); int revid = brevs.getRevisions().size(); int dotindex = runtimeName.length() - 4; if (dotindex > 0 && runtimeName.charAt(dotindex) == '.') { suffix = runtimeName.substring(runtimeName.length() - 4); runtimeName = runtimeName.substring(0, dotindex); } runtimeName += "-rev" + revid + suffix; } deployment.putAttachment(RUNTIME_NAME_KEY, runtimeName); } return runtimeName; } @SuppressWarnings("unchecked") private ServiceController<Phase> getDeferredPhaseService(DeploymentUnit depUnit) { ServiceName serviceName = DeploymentUtils.getDeploymentUnitPhaseServiceName(depUnit, Phase.FIRST_MODULE_USE); ServiceContainer serviceContainer = bundleManager.getServiceContainer(); return (ServiceController<Phase>) serviceContainer.getRequiredService(serviceName); } private final class RecreateCurrentRevisionPolicy implements BundleRefreshPolicy { private XBundle bundle; private VirtualFile rootFile; @Override public void startBundleRefresh(XBundle bundle) throws BundleException { this.bundle = bundle; XBundleRevision brev = bundle.getBundleRevision(); Deployment deployment = bundle.adapt(Deployment.class); try { InputStream inputStream = deployment.getRoot().getStreamURL().openStream(); rootFile = AbstractVFS.toVirtualFile(inputStream); } catch (IOException ex) { throw FrameworkMessages.MESSAGES.cannotObtainVirtualFile(ex); } bundle.putAttachment(BUNDLE_REFRESHING_KEY, Boolean.TRUE); undeployRevision(brev); } @Override public void refreshCurrentRevision(XBundleRevision brev) throws BundleException { // Create the revision {@link Deployment} DeploymentProvider deploymentManager = injectedDeploymentManager.getValue(); Deployment deployment = deploymentManager.createDeployment(bundle.getLocation(), rootFile); deployment.putAttachment(IntegrationConstants.BUNDLE_KEY, bundle); deployment.setAutoStart(false); String runtimeName = getRuntimeName(deployment); putDeployment(runtimeName, deployment); try { InputStream input = deployment.getRoot().openStream(); try { ServerDeploymentHelper server = new ServerDeploymentHelper(serverDeploymentManager); server.deploy(runtimeName, input); } finally { VFSUtils.safeClose(input); } } catch (RuntimeException rte) { throw rte; } catch (Exception ex) { throw MESSAGES.cannotDeployBundleRevision(ex, deployment); } } @Override public void endBundleRefresh(XBundle bundle) { bundle.removeAttachment(BUNDLE_REFRESHING_KEY); } } } }