/*
* Licensed 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.utils.extender;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* Base class to write bundle extenders.
* This extender tracks started bundles (or starting if they have a lazy activation
* policy) and will create an {@link Extension} for each of them to manage it.
*
* The extender will handle all concurrency and synchronization issues, see
* {@link Extension} for more information about the additional constraints.
*
* The extender guarantee that all extensions will be stopped synchronously with
* the STOPPING event of a given bundle and that all extensions will be stopped
* before the extender bundle is stopped.
*
*/
public abstract class AbstractExtender implements BundleActivator, BundleTrackerCustomizer, SynchronousBundleListener {
private final ConcurrentMap<Bundle, Extension> extensions = new ConcurrentHashMap<Bundle, Extension>();
private final ConcurrentMap<Bundle, FutureTask> destroying = new ConcurrentHashMap<Bundle, FutureTask>();
private volatile boolean stopping;
private volatile boolean stopped;
private boolean synchronous;
private boolean preemptiveShutdown;
private BundleContext context;
private ExecutorService executors;
private BundleTracker tracker;
/**
* Check if the extender is synchronous or not.
* If the flag is set, the extender will start the extension synchronously
* with the bundle being tracked or started. Else, the starting of the
* extension will be delegated to a thread pool.
*
* @return if the extender is synchronous
*/
public boolean isSynchronous() {
return synchronous;
}
/**
* Check if the extender performs a preemptive shutdown
* of all extensions when the framework is being stopped.
* The default behavior is to wait for the framework to stop
* the bundles and stop the extension at that time.
*
* @return if the extender use a preemptive shutdown
*/
public boolean isPreemptiveShutdown() {
return preemptiveShutdown;
}
public BundleContext getBundleContext() {
return context;
}
public ExecutorService getExecutors() {
return executors;
}
public void setSynchronous(boolean synchronous) {
this.synchronous = synchronous;
}
public void setPreemptiveShutdown(boolean preemptiveShutdown) {
this.preemptiveShutdown = preemptiveShutdown;
}
public boolean isStopping() {
return stopping;
}
public void start(BundleContext context) throws Exception {
this.context = context;
this.context.addBundleListener(this);
this.tracker = new BundleTracker(this.context, Bundle.ACTIVE | Bundle.STARTING, this);
if (!this.synchronous) {
this.executors = createExecutor();
}
doStart();
}
public void stop(BundleContext context) throws Exception {
stopping = true;
while (!extensions.isEmpty()) {
Collection<Bundle> toDestroy = chooseBundlesToDestroy(extensions.keySet());
if (toDestroy == null || toDestroy.isEmpty()) {
toDestroy = new ArrayList<Bundle>(extensions.keySet());
}
for (Bundle bundle : toDestroy) {
destroyExtension(bundle);
}
}
doStop();
if (executors != null) {
executors.shutdown();
try {
executors.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// Ignore
}
executors = null;
}
stopped = true;
}
protected void doStart() throws Exception {
startTracking();
}
protected void doStop() throws Exception {
stopTracking();
}
protected void startTracking() {
this.tracker.open();
}
protected void stopTracking() {
this.tracker.close();
}
/**
* Create the executor used to start extensions asynchronously.
*
* @return an
*/
protected ExecutorService createExecutor() {
return Executors.newScheduledThreadPool(3);
}
/**
*
* @param bundles
* @return
*/
protected Collection<Bundle> chooseBundlesToDestroy(Set<Bundle> bundles) {
return null;
}
public void bundleChanged(BundleEvent event) {
if (stopped) {
return;
}
Bundle bundle = event.getBundle();
if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) {
// The bundle is not in STARTING or ACTIVE state anymore
// so destroy the context. Ignore our own bundle since it
// needs to kick the orderly shutdown.
if (bundle != this.context.getBundle()) {
destroyExtension(bundle);
}
}
}
public Object addingBundle(Bundle bundle, BundleEvent event) {
modifiedBundle(bundle, event, bundle);
return bundle;
}
public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
// If the bundle being stopped is the system bundle,
// do an orderly shutdown of all blueprint contexts now
// so that service usage can actually be useful
if (context.getBundle(0).equals(bundle) && bundle.getState() == Bundle.STOPPING) {
if (preemptiveShutdown) {
try {
stop(context);
} catch (Exception e) {
error("Error while performing preemptive shutdown", e);
}
return;
}
}
if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) {
// The bundle is not in STARTING or ACTIVE state anymore
// so destroy the context. Ignore our own bundle since it
// needs to kick the orderly shutdown and not unregister the namespaces.
if (bundle != this.context.getBundle()) {
destroyExtension(bundle);
}
return;
}
// Do not track bundles given we are stopping
if (stopping) {
return;
}
// For starting bundles, ensure, it's a lazy activation,
// else we'll wait for the bundle to become ACTIVE
if (bundle.getState() == Bundle.STARTING) {
String activationPolicyHeader = (String) bundle.getHeaders().get(Constants.BUNDLE_ACTIVATIONPOLICY);
if (activationPolicyHeader == null || !activationPolicyHeader.startsWith(Constants.ACTIVATION_LAZY)) {
// Do not track this bundle yet
return;
}
}
createExtension(bundle);
}
public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
// Nothing to do
destroyExtension(bundle);
}
private void createExtension(final Bundle bundle) {
try {
BundleContext bundleContext = bundle.getBundleContext();
if (bundleContext == null) {
// The bundle has been stopped in the mean time
return;
}
final Extension extension = doCreateExtension(bundle);
if (extension == null) {
// This bundle is not to be extended
return;
}
synchronized (extensions) {
if (extensions.putIfAbsent(bundle, extension) != null) {
return;
}
}
if (synchronous) {
debug(bundle, "Starting extension synchronously");
extension.start();
} else {
debug(bundle, "Scheduling asynchronous start of extension");
getExecutors().submit(new Runnable() {
public void run() {
try {
extension.start();
} catch (Exception e) {
warn(bundle, "Error starting extension", e);
}
}
});
}
} catch (Throwable t) {
warn(bundle, "Error while creating extension", t);
}
}
private void destroyExtension(final Bundle bundle) {
FutureTask future;
synchronized (extensions) {
debug(bundle, "Starting destruction process");
future = destroying.get(bundle);
if (future == null) {
final Extension extension = extensions.remove(bundle);
if (extension != null) {
debug(bundle, "Scheduling extension destruction");
future = new FutureTask<Void>(new Runnable() {
public void run() {
debug(bundle, "Destroying extension");
try {
extension.destroy();
} catch (Exception e) {
warn(bundle, "Error while destroying extension", e);
} finally {
debug(bundle, "Finished destroying extension");
synchronized (extensions) {
destroying.remove(bundle);
}
}
}
}, null);
destroying.put(bundle, future);
} else {
debug(bundle, "Not an extended bundle or destruction of extension already finished");
}
} else {
debug(bundle, "Destruction already scheduled");
}
}
if (future != null) {
try {
debug(bundle, "Waiting for extension destruction");
future.run();
future.get();
} catch (Throwable t) {
warn(bundle, "Error while destroying extension", t);
}
}
}
/**
* Create the extension for the given bundle, or null if the bundle is not to be extended.
*
* @param bundle the bundle to extend
* @return
* @throws Exception
*/
protected abstract Extension doCreateExtension(Bundle bundle) throws Exception;
protected abstract void debug(Bundle bundle, String msg);
protected abstract void warn(Bundle bundle, String msg, Throwable t);
protected abstract void error(String msg, Throwable t);
}