/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.extender.internal.support;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.osgi.framework.Bundle;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
/**
* Map-like class with dedicated support for lazy bundles. Allows execution of operations on active bundles, promoting
* the lazy ones, if necessary, as fall back.
*
* The class is thread-safe.
*
* @author Costin Leau
* @param <T> the entity type associated with active bundles
*/
class LazyBundleRegistry<T> {
/** logger */
private final Log log;
/**
* A simple condition-like class.
*
* @author Costin Leau
*/
interface Condition {
/**
* Indicates if the given target bundle passes the condition or not.
*
* @param bundle target bundle
* @return true if the bundle passes the condition, false otherwise
*/
boolean pass(Bundle bundle);
}
/**
* Activator action performed on lazy bundles upon promotion.
*
* @author Costin Leau
* @param <V>
*/
interface Activator<V> {
/**
* Activates the given target bundle.
*
* @param <V> inherited return type
* @param bundle target bundle
* @return the bundle associated value
*/
V activate(Bundle bundle);
}
/**
* Operation performed all bundles - first active ones, followed by lazy ones (if nothing is found (null is
* returned)) by 'activating' them.
*
* @author Costin Leau
* @param <T>
* @param <V>
*/
interface Operation<T, V> {
/**
* Acts upon the bundle associated object (given as argument).
*
* @param object associated object
* @return the result of the operation
*/
V operate(T t) throws Exception;
}
/** active, valid bundles */
private final ConcurrentMap<Bundle, T> activeBundles = new ConcurrentHashMap<Bundle, T>(8);
/** lazy bundles (potentially invalid) */
private final ConcurrentMap<Bundle, Boolean> lazyBundles = new ConcurrentHashMap<Bundle, Boolean>(8);
/**
* Queue of bundles that have been activated and validated and should be removed from the lazy map. This is needed
* so that promoted bundles do not go unseen by threads using the method at that point.
*/
private final List<Bundle> promotionQueue = new ArrayList<Bundle>(4);
/** counter used for determining the promotion thread */
private volatile AtomicInteger threadCounter = new AtomicInteger();
private final Condition condition;
private final Activator<T> activator;
LazyBundleRegistry(Condition promotionCondition, Activator<T> activator, Log log) {
this.condition = promotionCondition;
this.activator = activator;
this.log = log;
}
void add(Bundle bundle, boolean isLazy, boolean applyCondition) {
if (isLazy) {
lazyBundles.put(bundle, Boolean.valueOf(applyCondition));
} else {
activeBundles.put(bundle, activator.activate(bundle));
}
}
boolean remove(Bundle bundle) {
boolean value = false;
value = (activeBundles.remove(bundle) != null);
value |= (lazyBundles.remove(bundle) != null);
synchronized (promotionQueue) {
value |= promotionQueue.remove(bundle);
}
return value;
}
/**
* Applies an operation on all the bundles. To cope with concurrent environment, the class uses several queues so
* that lazy bundles that get activated, do not get ignored by threads hitting the method right at that point.
*
* @param <V>
* @param action
* @return
*/
<V> V apply(Operation<T, V> action) throws Exception {
boolean debug = log.isDebugEnabled();
// count running thread
threadCounter.incrementAndGet();
try {
// check active bundles
for (Iterator<T> i = activeBundles.values().iterator(); i.hasNext();) {
T result = i.next();
V value = action.operate(result);
if (value != null) {
return value;
}
}
// nothing found, look into lazy bundles
for (Iterator<Map.Entry<Bundle, Boolean>> i = lazyBundles.entrySet().iterator(); i.hasNext();) {
Entry<Bundle, Boolean> entry = i.next();
Bundle bundle = entry.getKey();
Boolean applyCondition = entry.getValue();
if (Boolean.FALSE.equals(applyCondition)
|| (Boolean.TRUE.equals(applyCondition) && condition.pass(bundle))) {
// promote bundle
T result = activeBundles.putIfAbsent(bundle, activator.activate(bundle));
if (result == null) {
result = activeBundles.get(bundle);
synchronized (promotionQueue) {
promotionQueue.add(bundle);
}
}
if (result != null) {
V value = action.operate(result);
if (value != null) {
return value;
}
}
} else {
// the bundle is not compatible, remove it asap
lazyBundles.remove(bundle);
if (debug)
log.debug("Activated lazy bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle)
+ " but found it unsuitable");
}
}
// nothing found
return null;
}
// cleanup the promotion queue
finally {
Bundle[] loggedBundles = null;
synchronized (promotionQueue) {
// the last thread accessing the method, does the cleanup
// this way, we know other threads will pick the most up to date state
// of the concurrent map
if (threadCounter.decrementAndGet() == 0) {
for (Bundle bundle : promotionQueue) {
// remove bundle from the lazy map
// but check that it wasn't removed in the meantime
// by #remove(Bundle)
if (lazyBundles.remove(bundle) == null) {
activeBundles.remove(bundle);
}
}
}
if (debug && !promotionQueue.isEmpty()) {
loggedBundles = (Bundle[]) promotionQueue.toArray(new Bundle[promotionQueue.size()]);
}
promotionQueue.clear();
}
if (loggedBundles != null) {
StringBuilder builder = new StringBuilder("Activated (and validated) lazy bundles [ ");
for (Bundle bundle : loggedBundles) {
builder.append(OsgiStringUtils.nullSafeNameAndSymName(bundle));
builder.append(" ");
}
builder.append("]");
log.debug(builder);
}
}
}
public void clear() {
promotionQueue.clear();
lazyBundles.clear();
activeBundles.clear();
}
}