| /* |
| * 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 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; |
| |
| 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; |
| |
| /** |
| * 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); |
| } |
| |
| 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 The extension |
| * @throws Exception If something goes wrong |
| */ |
| 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); |
| |
| } |