/*
* 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.aries.quiesce.manager.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.aries.quiesce.manager.QuiesceCallback;
import org.apache.aries.quiesce.manager.QuiesceManager;
import org.apache.aries.quiesce.participant.QuiesceParticipant;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class QuiesceManagerImpl implements QuiesceManager {
/** Logger */
private static final Logger LOGGER = LoggerFactory.getLogger(QuiesceManagerImpl.class.getName());
/** The default timeout to use */
private static int defaultTimeout = 60000;
/** The container's {@link BundleContext} */
private BundleContext bundleContext = null;
/** The thread pool to execute timeout commands */
private ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(10);
/** The thread pool to execute quiesce commands */
private ExecutorService executor = new ThreadPoolExecutor(0, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),new ThreadFactory() {
public Thread newThread(Runnable arg0) {
Thread t = new Thread(arg0, "Quiesce Manager Thread");
t.setDaemon(true);
return t;
}
});
/** The map of bundles that are currently being quiesced */
private static ConcurrentHashMap<Long, Bundle> bundleMap = new ConcurrentHashMap<Long, Bundle>();
public QuiesceManagerImpl(BundleContext bc) {
bundleContext = bc;
}
/**
* Attempts to quiesce all bundles in the list. After the timeout has elapsed,
* or if successfully quiesced before that, the bundles are stopped. This method
* is non-blocking. Calling objects wishing to track the state of the bundles
* need to listen for the resulting stop events.
*/
public void quiesce(long timeout, List<Bundle> bundles) {
if (bundles != null && !!!bundles.isEmpty()) {
//check that bundle b is not already quiescing
Iterator<Bundle> it = bundles.iterator();
Set<Bundle> bundlesToQuiesce = new HashSet<Bundle>();
while(it.hasNext()) {
Bundle b = it.next();
Bundle priorBundle = bundleMap.putIfAbsent(b.getBundleId(), b);
if (priorBundle == null) {
bundlesToQuiesce.add(b);
}else{
LOGGER.warn("Already quiescing bundle "+ b.getSymbolicName());
}
}
Runnable command = new BundleQuiescer(bundlesToQuiesce, timeout, bundleMap);
executor.execute(command);
}
}
/**
* Attempts to quiesce all bundles in the list, using the default timeout.
* After the timeout has elapsed, or if successfully quiesced before that,
* the bundles are stopped. This method is non-blocking. Calling objects
* wishing to track the state of the bundles need to listen for the
* resulting stop events.
*/
public void quiesce(List<Bundle> bundlesToQuiesce) {
quiesce(defaultTimeout, bundlesToQuiesce);
}
private static boolean stopBundle(Bundle bundleToStop) {
try {
bundleToStop.stop();
bundleMap.remove(bundleToStop.getBundleId());
}catch (BundleException be) {
return false;
}
return true;
}
/**
* BundleQuiescer is used for each bundle to quiesce. It creates a callback object for each
* participant. Well-behaved participants will be non-blocking on their quiesce method.
* When all callbacks for the participants have completed, this thread will get an
* interrupt, so it sleeps until it hits the timeout. When complete it stops the bundle
* and removes the bundles from the list of those that are being quiesced.
*/
private class BundleQuiescer implements Runnable {
private Set<Bundle> bundlesToQuiesce;
private long timeout;
public BundleQuiescer(Set<Bundle> bundlesToQuiesce, long timeout, ConcurrentHashMap<Long, Bundle> bundleMap) {
this.bundlesToQuiesce = new HashSet<Bundle>(bundlesToQuiesce);
this.timeout = timeout;
}
public void run() {
try {
if (bundleContext != null) {
ServiceReference[] serviceRefs = bundleContext.getServiceReferences(QuiesceParticipant.class.getName(), null);
if (serviceRefs != null) {
List<QuiesceParticipant> participants = new ArrayList<QuiesceParticipant>();
final List<QuiesceCallbackImpl> callbacks = new ArrayList<QuiesceCallbackImpl>();
Set<Bundle> copyOfBundles = new HashSet<Bundle>(bundlesToQuiesce);
Timer timer = new Timer();
//Create callback objects for all participants
for( ServiceReference sr : serviceRefs ) {
QuiesceParticipant participant = (QuiesceParticipant) bundleContext.getService(sr);
participants.add(participant);
callbacks.add(new QuiesceCallbackImpl(copyOfBundles, callbacks, timer));
}
//Quiesce each participant and wait for an interrupt from a callback
//object when all are quiesced, or the timeout to be reached
for( int i=0; i<participants.size(); i++ ) {
QuiesceParticipant participant = participants.get(i);
QuiesceCallbackImpl callback = callbacks.get(i);
List<Bundle> participantBundles = new ArrayList<Bundle>();
//deep copy
for (Bundle b : copyOfBundles) {
participantBundles.add(b);
}
participant.quiesce(callback, participantBundles);
}
timer.schedule(new TimerTask() {
@Override
public void run() {
//stop bundles
//go through callbacks and cancel all bundles
for ( Enumeration<Bundle> remainingBundles = bundleMap.elements(); remainingBundles.hasMoreElements(); ) {
Bundle b = remainingBundles.nextElement();
LOGGER.warn("Could not quiesce, so stopping bundle "+ b.getSymbolicName());
stopBundle(b);
}
/*
for ( QuiesceCallbackImpl cb : callbacks ) {
System.out.println("Clearing callback");
cb.clear();
}
*/
}
}, timeout);
}else{
LOGGER.warn("No participants, so stopping bundles");
for ( Enumeration<Bundle> remainingBundles = bundleMap.elements(); remainingBundles.hasMoreElements(); ) {
Bundle b = remainingBundles.nextElement();
stopBundle(b);
}
}
}
} catch (InvalidSyntaxException e) {
LOGGER.warn("Exception trying to get service references for quiesce participants "+ e.getMessage());
}
}
}
/**
* Callback object provided for each participant for each quiesce call
* from the quiesce manager.
*/
private static class QuiesceCallbackImpl implements QuiesceCallback {
//Must be a copy
private final Set<Bundle> toQuiesce;
//Must not be a copy
private final List<QuiesceCallbackImpl> allCallbacks;
//Timer so we can cancel the alarm if all done
private final Timer timer;
public QuiesceCallbackImpl(Collection<Bundle> toQuiesce, List<QuiesceCallbackImpl> allCallbacks, Timer timer)
{
this.toQuiesce = new HashSet<Bundle>(toQuiesce);
this.allCallbacks = allCallbacks;
this.timer = timer;
}
public void clear() {
// TODO Auto-generated method stub
}
/**
* Removes the bundles from the list of those to quiesce.
* If the list is now empty, this callback object is finished (i.e.
* the participant linked to this object has quiesced all the bundles
* requested).
*
* If all other participants have also completed, then the
* calling BundleQuieser thread is interrupted.
*/
public void bundleQuiesced(Bundle... bundlesQuiesced) {
synchronized (allCallbacks) {
for(Bundle b : bundlesQuiesced) {
if(toQuiesce.remove(b)) {
if(checkOthers(b)){
QuiesceManagerImpl.stopBundle(b);
if(allCallbacksComplete()){
timer.cancel();
}
}
}
}
}
}
private boolean checkOthers(Bundle b) {
boolean allDone = true;
Iterator<QuiesceCallbackImpl> it = allCallbacks.iterator();
while (allDone && it.hasNext()) {
allDone = !!!it.next().toQuiesce.contains(b);
}
return allDone;
}
private boolean allCallbacksComplete() {
boolean allDone = true;
Iterator<QuiesceCallbackImpl> it = allCallbacks.iterator();
while (allDone && it.hasNext()) {
allDone = !!!it.next().toQuiesce.isEmpty();
}
return allDone;
}
}
}