/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.webconsole.internal.core; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.apache.felix.webconsole.SimpleWebConsolePlugin; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.service.log.LogService; import org.osgi.service.packageadmin.PackageAdmin; import org.osgi.service.startlevel.StartLevel; abstract class BaseUpdateInstallHelper implements Runnable { private final SimpleWebConsolePlugin plugin; private final File bundleFile; private final boolean refreshPackages; private Thread updateThread; BaseUpdateInstallHelper( SimpleWebConsolePlugin plugin, String name, File bundleFile, boolean refreshPackages ) { this.plugin = plugin; this.bundleFile = bundleFile; this.refreshPackages = refreshPackages; this.updateThread = new Thread( this, name ); this.updateThread.setDaemon( true ); } protected File getBundleFile() { return bundleFile; } protected abstract Bundle doRun( InputStream bundleStream ) throws BundleException; protected final Object getService( String serviceName ) { return plugin.getService( serviceName ); } protected final SimpleWebConsolePlugin getLog() { return plugin; } protected Bundle getTargetBundle() { return null; } /** * @return the installed bundle or <code>null</code> if no bundle was touched * @throws BundleException * @throws IOException */ protected Bundle doRun() throws Exception { // now deploy the resolved bundles InputStream bundleStream = null; try { bundleStream = new FileInputStream( bundleFile ); return doRun( bundleStream ); } finally { IOUtils.closeQuietly( bundleStream ); } } final void start() { if ( updateThread != null ) { updateThread.start(); } } public final void run() { // now deploy the resolved bundles try { // we need the package admin before we call the bundle // installation or update, since we might be updating // our selves in which case the bundle context will be // invalid by the time we want to call the update PackageAdmin pa = ( refreshPackages ) ? ( PackageAdmin ) getService( PackageAdmin.class.getName() ) : null; // same for the startlevel StartLevel startLevel = null; Bundle bundle = getTargetBundle(); int state = pa != null && bundle != null ? bundle.getState() : 0; int startFlags = 0; // If the bundle has been started we want to stop it first, then update it, refresh it, and restart it // because otherwise, it will be stopped and started twice (once by the update and once by the refresh) if ((state & (Bundle.ACTIVE | Bundle.STARTING)) != 0) { // we need the StartLevel service before we stop the bundle // before the update, since we might be stopping // our selves in which case the bundle context will be // invalid by the time we want to call the startlevel startLevel = (StartLevel) getService(StartLevel.class.getName()); // We want to start the bundle afterwards without affecting the persistent state of the bundle // However, we can only use the transient options if the framework startlevel is not less than the // bundle startlevel (in case that there is no starlevel service we assume we are good). if (startLevel == null || startLevel.getStartLevel() >= startLevel.getBundleStartLevel(bundle)) { startFlags |= Bundle.START_TRANSIENT; } // If the bundle is in the starting state it might be lazy and not started yet - hence, start it // according to its policy. if (state == Bundle.STARTING) { startFlags |= Bundle.START_ACTIVATION_POLICY; } // We stop the bundle transiently - assuming we can also start it transiently later (see above) in which // case we didn't mess with its persistent state at all. bundle.stop(Bundle.STOP_TRANSIENT); } // We want to catch an exception during update to be able to restart the bundle if we stopped it previously Exception rethrow = null; try { // perform the action! bundle = doRun(); if ( pa != null && bundle != null ) { // refresh packages and give it at most 5 seconds to finish refreshPackages( pa, plugin.getBundle().getBundleContext(), 5000L, bundle ); } } catch (Exception ex) { rethrow = ex; throw ex; } finally { // If we stopped the bundle lets try to restart it (we created the correct flags above already). if ((state & (Bundle.ACTIVE | Bundle.STARTING)) != 0) { try { bundle.start(startFlags); } catch (Exception ex) { if (rethrow == null) { throw ex; } else { try { getLog().log( LogService.LOG_ERROR, "Cannot restart bundle: " + bundle + " after exception during update!", ex); } catch ( Exception secondary ) { // at the time this exception happens the log used might have // been destroyed and is not available to use any longer. So // we only can write to stderr at this time to at least get // some message out ... System.err.println( "Cannot restart bundle: " + bundle + " after exception during update!"); ex.printStackTrace( System.err ); } } } } } } catch ( Exception e ) { try { getLog().log( LogService.LOG_ERROR, "Cannot install or update bundle from " + bundleFile, e ); } catch ( Exception secondary ) { // at the time this exception happens the log used might have // been destroyed and is not available to use any longer. So // we only can write to stderr at this time to at least get // some message out ... System.err.println( "Cannot install or update bundle from " + bundleFile ); e.printStackTrace( System.err ); } } finally { if ( bundleFile != null ) { bundleFile.delete(); } // release update thread for GC updateThread = null; } } /** * This is an utility method that issues refresh package instruction to the framework. * * @param packageAdmin is the package admin service obtained using the bundle context of the caller. * If this is <code>null</code> no refresh packages is performed * @param bundleContext of the caller. This is needed to add a framework listener. * @param maxWait the maximum time to wait for the packages to be refreshed * @param bundle the bundle, which packages to refresh or <code>null</code> to refresh all packages. * @return true if refresh is succesfull within the given time frame */ static boolean refreshPackages(final PackageAdmin packageAdmin, final BundleContext bundleContext, final long maxWait, final Bundle bundle) { return new RefreshPackageTask().refreshPackages( packageAdmin, bundleContext, maxWait, bundle ); } static class RefreshPackageTask implements FrameworkListener { private volatile boolean refreshed = false; private final Object lock = new Object(); boolean refreshPackages( final PackageAdmin packageAdmin, final BundleContext bundleContext, final long maxWait, final Bundle bundle ) { if (null == packageAdmin) { return false; } if (null == bundleContext) { return false; } bundleContext.addFrameworkListener(this); if (null == bundle) { packageAdmin.refreshPackages(null); } else { packageAdmin.refreshPackages(new Bundle[] { bundle }); } try { // check for spurious wait long start = System.currentTimeMillis(); long delay = maxWait; while (!refreshed && delay > 0) { synchronized (lock) { if ( !refreshed ) { lock.wait(delay); // remove the time we already waited for delay = maxWait - (System.currentTimeMillis() - start); } } } } catch (final InterruptedException e) { // just return if the thread is interrupted Thread.currentThread().interrupt(); } finally { bundleContext.removeFrameworkListener(this); } return refreshed; } public void frameworkEvent(final FrameworkEvent e) { if (e.getType() == FrameworkEvent.PACKAGES_REFRESHED) { synchronized (lock) { refreshed = true; lock.notifyAll(); } } } } }