/*******************************************************************************
* Copyright (c) 2009 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cambridge Semantics Incorporated - initial API and implementation
*******************************************************************************/
package org.openanzo.osgi;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.rdf.utils.CopyOnWriteMultiHashMap;
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.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.LoggerFactory;
/**
* Abstract service activator that doesn't start until all dependencies are met via trackers
*
* @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com</a>)
*
*/
public abstract class ServiceActivator implements BundleActivator, IStatusProvider {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(ServiceActivator.class);
protected BundleContext context = null;
protected ServiceLifecycleState state = ServiceLifecycleState.CREATED;
/**
* @return the state
*/
public ServiceLifecycleState getState() {
return state;
}
protected final ReentrantLock lock = new ReentrantLock();
private final StartRunner startRunner = new StartRunner();
protected Thread startThread = null;
private final Map<String, ServiceTracker> trackers = new ConcurrentHashMap<String, ServiceTracker>();
protected EventAdmin eventAdmin = null;
private final CopyOnWriteMultiHashMap<String, ServiceReference> dependentServiceReferences = new CopyOnWriteMultiHashMap<String, ServiceReference>();
private final Hashtable<ServiceReference, Object> dependentServices = new Hashtable<ServiceReference, Object>();
/** Topic for event admin messages about status of service */
public static final String TOPIC = "org/openanzo/serviceActivator";
String bundleName;
String bundleDescription;
/**
* Should this service be started in its own thread
*
* @return true if this service should be started in its own thread
*/
public boolean startThreaded() {
return true;
}
/**
*
* @return True if this activator wants to listener for the framework stopping events
*/
public boolean registerFrameworkStoppingListener() {
return false;
}
/**
* Does this service depend on the system config service before it can start
*
* @return true if this service depends on the system config service before it can start
*/
public boolean dependsOnSysconfig() {
return true;
}
static final private Set<String> serviceClassNames = new HashSet<String>();
static {
serviceClassNames.add(ServiceActivator.class.getName());
serviceClassNames.add(IStatusProvider.class.getName());
}
protected Collection<String> getServiceClassNames() {
HashSet<String> scn = new HashSet<String>(serviceClassNames);
scn.add(this.getClass().getName());
return scn;
}
private void addTracker(final String dependency) {
ServiceTracker tracker = new ServiceTracker(context, dependency, null) {
@Override
public Object addingService(ServiceReference reference) {
Object service = context.getService(reference);
lock.lock();
try {
dependentServiceReferences.put(dependency, reference);
dependentServices.put(reference, service);
} finally {
lock.unlock();
}
serviceAdded(dependency, reference, service);
return service;
}
@Override
public void removedService(ServiceReference reference, Object serviceObject) {
lock.lock();
try {
dependentServiceReferences.remove(dependency, reference);
dependentServices.remove(reference);
} finally {
lock.unlock();
}
context.ungetService(reference);
serviceRemoved(dependency, reference, serviceObject);
}
};
tracker.open();
trackers.put(dependency, tracker);
}
private void addFilteredTracker(final FilteredDependency dependency) {
try {
ServiceTracker tracker = new ServiceTracker(context, context.createFilter(dependency.filter), null) {
@Override
public Object addingService(ServiceReference reference) {
Object service = context.getService(reference);
lock.lock();
try {
dependentServiceReferences.put(dependency.type, reference);
dependentServices.put(reference, service);
} finally {
lock.unlock();
}
serviceAdded(dependency.type, reference, service);
return service;
}
@Override
public void removedService(ServiceReference reference, Object serviceObject) {
lock.lock();
try {
dependentServiceReferences.remove(dependency.type, reference);
dependentServices.remove(reference);
} finally {
lock.unlock();
}
context.ungetService(reference);
serviceRemoved(dependency.type, reference, serviceObject);
}
};
tracker.open();
trackers.put(dependency.filter, tracker);
} catch (InvalidSyntaxException ise) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error registering filtered dependency", ise);
}
}
public void start(BundleContext bundleContext) throws Exception {
context = bundleContext;
Dictionary<?, ?> dictionary = context.getBundle().getHeaders();
bundleName = (String) dictionary.get(Constants.BUNDLE_NAME);
bundleDescription = (String) dictionary.get(Constants.BUNDLE_DESCRIPTION);
if (bundleName == null) {
bundleName = context.getBundle().getSymbolicName();
}
if (bundleDescription == null) {
bundleDescription = "Bundle for " + bundleName;
}
for (String dependency : getDependencies()) {
addTracker(dependency);
}
for (FilteredDependency dependency : getFilteredDependencies()) {
addFilteredTracker(dependency);
}
addTracker(EventAdmin.class.getName());
if (dependsOnSysconfig()) {
ServiceTracker tracker = new ServiceTracker(bundleContext, ISystemConfig.class.getName(), null) {
@Override
public Object addingService(ServiceReference reference) {
Object service = context.getService(reference);
lock.lock();
try {
dependentServiceReferences.put(ISystemConfig.class.getName(), reference);
dependentServices.put(reference, service);
} finally {
lock.unlock();
}
serviceAdded(ISystemConfig.class.getName(), reference, service);
if (registerService()) {
lock.lock();
try {
Properties props = new Properties();
props.put(org.osgi.framework.Constants.SERVICE_PID, getServicePid());
props.put(org.osgi.framework.Constants.SERVICE_DESCRIPTION, getBundleDescription());
context.registerService(getServiceClassNames().toArray(new String[0]), ServiceActivator.this, props);
} finally {
lock.unlock();
}
}
return service;
}
@Override
public void removedService(ServiceReference reference, Object serviceObject) {
lock.lock();
try {
dependentServiceReferences.remove(ISystemConfig.class.getName(), reference);
dependentServices.remove(reference);
} finally {
lock.unlock();
}
context.ungetService(reference);
serviceRemoved(ISystemConfig.class.getName(), reference, serviceObject);
}
};
tracker.open();
trackers.put(ISystemConfig.class.getName(), tracker);
} else {
if (registerService()) {
lock.lock();
try {
Properties props = new Properties();
props.put(org.osgi.framework.Constants.SERVICE_PID, getServicePid());
props.put(org.osgi.framework.Constants.SERVICE_DESCRIPTION, getBundleDescription());
context.registerService(getServiceClassNames().toArray(new String[0]), ServiceActivator.this, props);
} finally {
lock.unlock();
}
}
}
if (registerFrameworkStoppingListener()) {
final Bundle systemBundle = context.getBundle(0);
systemBundle.getBundleContext().addBundleListener(new SynchronousBundleListener() {
public void bundleChanged(BundleEvent event) {
if (event.getBundle().equals(systemBundle) && event.getType() == BundleEvent.STOPPING) {
frameworkStopping();
}
}
});
}
if (isInitialized()) {
startLocked();
}
}
/**
* Override if bundle needs to do something when framework is stopping
*/
protected void frameworkStopping() {
}
@SuppressWarnings("unchecked")
protected <Service> Service getDependency(Class<Service> type) {
lock.lock();
try {
Collection<ServiceReference> depdencies = dependentServiceReferences.get(type.getName());
if (depdencies != null && depdencies.size() > 0) {
Service srv = (Service) dependentServices.get(depdencies.iterator().next());
return srv;
} else {
return null;
}
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")
protected <Service> Map<ServiceReference, Service> getDependencies(Class<Service> type) {
lock.lock();
try {
Collection<ServiceReference> refs = dependentServiceReferences.get(type.getName());
HashMap<ServiceReference, Service> map = new HashMap<ServiceReference, Service>();
if (refs != null) {
for (ServiceReference ref : refs) {
map.put(ref, (Service) dependentServices.get(ref));
}
}
return map;
} finally {
lock.unlock();
}
}
private void serviceAdded(String type, ServiceReference reference, Object service) {
if (type.equals(EventAdmin.class.getName())) {
eventAdmin = (EventAdmin) service;
}
serviceAvailable(type, reference, service);
if (isInitialized()) {
startLocked();
} else {
fireEvent();
}
}
private void serviceRemoved(String type, ServiceReference reference, Object service) {
serviceUnAvailable(type, reference, service);
if (isInitialized()) {
stopLocked(false);
} else {
fireEvent();
}
}
public void stop(BundleContext context) throws Exception {
for (ServiceTracker tracker : trackers.values()) {
tracker.close();
}
trackers.clear();
dependentServices.clear();
stopLocked(true);
}
/**
* Are all the requirements for starting this service met
*
* @return true if all the requirements for starting this service met
*/
public boolean isInitialized() {
lock.lock();
try {
for (String dependency : getDependencies()) {
if (dependentServiceReferences.get(dependency) == null || dependentServiceReferences.get(dependency).size() == 0)
return false;
}
for (FilteredDependency dependency : getFilteredDependencies()) {
if (dependentServiceReferences.get(dependency.type) == null || dependentServiceReferences.get(dependency.type).size() == 0)
return false;
}
if (dependsOnSysconfig() && dependentServiceReferences.get(ISystemConfig.class.getName()) == null) {
return false;
}
if (eventAdmin == null) {
return false;
}
} finally {
lock.unlock();
}
return true;
}
/**
* Get all the required dependencies that are satisfied
*
* @return all the required dependencies that are satisfied
*/
public List<String> getOkServices() {
ArrayList<String> ok = new ArrayList<String>();
for (String dependency : getDependencies()) {
if (dependentServiceReferences.get(dependency) != null && dependentServiceReferences.get(dependency).size() > 0) {
ok.add(dependency);
}
}
for (FilteredDependency dependency : getFilteredDependencies()) {
if (dependentServiceReferences.get(dependency.type) != null && dependentServiceReferences.get(dependency.type).size() > 0) {
ok.add(dependency.type);
}
}
return ok;
}
/**
* Get all the required dependencies that are not yet satisfied
*
* @return all the required dependencies that are not yet satisfied
*/
public List<String> getWaitingServices() {
ArrayList<String> waiting = new ArrayList<String>();
for (String dependency : getDependencies()) {
if (dependentServiceReferences.get(dependency) == null || dependentServiceReferences.get(dependency).size() == 0) {
waiting.add(dependency);
}
}
for (FilteredDependency dependency : getFilteredDependencies()) {
if (dependentServiceReferences.get(dependency.type) == null || dependentServiceReferences.get(dependency.type).size() == 0) {
waiting.add(dependency.type);
}
}
return waiting;
}
/**
* Get all the optional dependencies that are satisfied
*
* @return all the optional dependencies that are satisfied
*/
public List<String> getOptionalOkServices() {
ArrayList<String> ok = new ArrayList<String>();
return ok;
}
/**
* Get all the optional dependencies that are not yet satisfied
*
* @return all the optional dependencies that are not yet satisfied
*/
public List<String> getOptionalWaitingServices() {
ArrayList<String> waiting = new ArrayList<String>();
return waiting;
}
/**
* Get any extra status text to append to the default status text
*
* @param html
* true if the status text should be formated in html
* @return any extra status text for this service
*/
public String getExtraStatus(boolean html) {
return null;
}
protected void fireEvent() {
if (eventAdmin != null) {
Properties props = new Properties();
props.put("className", ServiceActivator.this.getClass().getName());
eventAdmin.postEvent(new Event(TOPIC, (Dictionary<Object, Object>) props));
}
}
public String getCurrentStatus(boolean html) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
Object pid = getServicePid();
Object serviceDesc = context.getBundle().getHeaders().get(Constants.SERVICE_DESCRIPTION);
if (html) {
List<String> ok = getOkServices();
List<String> waiting = getWaitingServices();
List<String> okOptional = getOptionalOkServices();
List<String> waitingOptional = getOptionalWaitingServices();
ServiceLifecycleState state = getState();
String status = null;
switch (state) {
case CREATED:
status = "<font color='#0000cc'>" + state.toString() + "</font>";
break;
case STARTED:
status = "<font color='#00cc00'>" + state.toString() + "</font>";
break;
case STARTING:
status = "<font color='#FFFF00'>" + state.toString() + "</font>";
break;
case STOPPED:
status = "<font color='#cc0000'>" + state.toString() + "</font>";
break;
case NOT_ENABLED:
status = "<font color='#cc0000'>" + state.toString() + "</font>";
break;
case STOPPING:
status = "<font color='#FF9900'>" + state.toString() + "</font>";
break;
}
pw.println("<div id='" + pid + "'>");
pw.println("<h2>" + ((pid != null) ? pid : "") + " - " + status + "</h2><div id='sub" + pid + "'>" + ((serviceDesc != null) ? serviceDesc : ""));
pw.println("<br/>Bundle: [" + context.getBundle().getBundleId() + "] " + context.getBundle().getLocation());
pw.println("<br/>Version: [" + context.getBundle().getVersion().toString() + "] LastModified: [" + DateFormat.getDateTimeInstance().format(new Date(context.getBundle().getLastModified())) + "]");
if (ok != null && ok.size() > 0) {
pw.println("<h4><font color='#00cc00'>Services that are already available:</font></h4>");
TreeSet<String> set = new TreeSet<String>(ok);
for (String srv : set) {
pw.println("<li>" + srv + "</li>");
}
}
if (okOptional != null && okOptional.size() > 0) {
pw.println("<h4><font color='#00cc00'>Optional services that are already available:</font></h4>");
TreeSet<String> set = new TreeSet<String>(okOptional);
for (String srv : set) {
pw.println("<li>" + srv + "</li>");
}
}
if (waiting != null && waiting.size() > 0) {
pw.println("<h4><font color='#cc0000'>Services that are not available yet:</font></h4>");
TreeSet<String> set = new TreeSet<String>(waiting);
for (String srv : set) {
pw.println("<li>" + srv + "</li>");
}
}
if (waitingOptional != null && waitingOptional.size() > 0) {
pw.println("<h4><font color='#cc0000'>Optional services that are not available yet:</font></h4>");
TreeSet<String> set = new TreeSet<String>(waitingOptional);
for (String srv : set) {
pw.println("<li>" + srv + "</li>");
}
}
String cs = getExtraStatus(true);
if (cs != null) {
pw.println(cs);
}
pw.println("</div></div><hr width='100%' size='1' />");
} else {
pw.println("**********************************************************");
pw.println("Bundle: [" + context.getBundle().getBundleId() + "] " + context.getBundle().getLocation());
pw.println("Service: [" + ((pid != null) ? pid : "") + "] " + ((serviceDesc != null) ? serviceDesc : ""));
pw.println("Version: [" + context.getBundle().getVersion().toString() + "] LastModified: [" + DateFormat.getDateTimeInstance().format(new Date(context.getBundle().getLastModified())) + "]");
pw.println(getServiceStatus());
}
pw.flush();
sw.flush();
return sw.toString();
}
private String getServiceStatus() {
StringBuilder result = new StringBuilder();
List<String> ok = getOkServices();
List<String> waiting = getWaitingServices();
List<String> okOptional = getOptionalOkServices();
List<String> waitingOptional = getOptionalWaitingServices();
result.append("State:" + getState() + "\n");
if (ok != null && ok.size() > 0) {
result.append("Available Required Services:\n");
for (String serv : ok) {
result.append("\t");
result.append(serv);
result.append("\n");
}
}
if (waiting != null && waiting.size() > 0) {
result.append("Waiting for Required Services:\n");
for (String serv : waiting) {
result.append("\t");
result.append(serv);
result.append("\n");
}
}
if (okOptional != null && okOptional.size() > 0) {
result.append("Available Optional Services:\n");
for (String serv : okOptional) {
result.append("\t");
result.append(serv);
result.append("\n");
}
}
if (waitingOptional != null && waitingOptional.size() > 0) {
result.append("Unavailable Optional Services:\n");
for (String serv : waitingOptional) {
result.append("\t");
result.append(serv);
result.append("\n");
}
}
result.append("\n");
String status = getExtraStatus(false);
if (status != null && status.length() > 0) {
result.append("Extra Status Info:\n");
result.append(status);
result.append("\n");
}
return result.toString();
}
final protected void startLocked() {
if (startThreaded()) {
lock.lock();
try {
if (startThread == null) {
startThread = new Thread(startRunner, "ServiceInitializer:" + getServicePid());
startThread.start();
}
} finally {
lock.unlock();
}
} else {
startRunner.run();
}
}
final protected void stopLocked(boolean bundleStopping) {
StopRunner runner = new StopRunner(bundleStopping);
runner.run();
}
class StartRunner implements Runnable {
public void run() {
lock.lock();
ServiceLifecycleState prevState = state;
try {
if (state != ServiceLifecycleState.STARTED && state != ServiceLifecycleState.STARTING) {
state = ServiceLifecycleState.STARTING;
log.info(LogUtils.LIFECYCLE_MARKER, "Starting " + ServiceActivator.this.getClass().getName());
start();
log.info(LogUtils.LIFECYCLE_MARKER, "Started " + ServiceActivator.this.getClass().getName());
state = ServiceLifecycleState.STARTED;
}
fireEvent();
} catch (Throwable t) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error starting bundle activator:" + ServiceActivator.this.getClass().getName(), t);
state = prevState;
} finally {
startThread = null;
lock.unlock();
}
}
}
class StopRunner implements Runnable {
private final boolean bundleStopping;
StopRunner(boolean bundleStopping) {
this.bundleStopping = bundleStopping;
}
public void run() {
lock.lock();
try {
if (state == ServiceLifecycleState.STARTED) {
state = ServiceLifecycleState.STOPPING;
log.info(LogUtils.LIFECYCLE_MARKER, "Stopping " + ServiceActivator.this.getClass().getName());
stop(bundleStopping);
log.info(LogUtils.LIFECYCLE_MARKER, "Stopped " + ServiceActivator.this.getClass().getName());
}
fireEvent();
} catch (Throwable t) {
log.error(LogUtils.LIFECYCLE_MARKER, "Error stopping bundle activator:" + ServiceActivator.this.getClass().getName(), t);
} finally {
state = ServiceLifecycleState.STOPPED;
lock.unlock();
}
}
}
public String getBundleName() {
return bundleName;
}
public String getBundleDescription() {
return bundleDescription;
}
/**
* Start the service
*/
public abstract void start() throws AnzoException;
/**
* Stop the service
*
* @param bundleStopping
* true if bundle is being specifically stopped
*/
public abstract void stop(boolean bundleStopping) throws AnzoException;
/**
* A dependent service of the given type is available
*
* @param type
* type of service available
* @param service
* service object
*/
public void serviceAvailable(String type, ServiceReference reference, Object service) {
}
/**
* A dependent service of the given type is available
*
* @param type
* type of service available
* @param service
* service object
*/
public void serviceUnAvailable(String type, ServiceReference reference, Object service) {
}
/**
* Provide the list of dependencies
*
* @return the list of dependencies
*/
public abstract String[] getDependencies();
/**
* Provide the list of dependencies
*
* @return the list of dependencies
*/
public FilteredDependency[] getFilteredDependencies() {
return new FilteredDependency[0];
}
/**
* Get the service pid for this service
*
* @return the service pid for this service
*/
public abstract String getServicePid();
/**
* Should this service register itself with the osgi system as a service
*
* @return true if this service register itself with the osgi system as a service
*/
public boolean registerService() {
return true;
}
public class FilteredDependency {
String type;
String filter;
public FilteredDependency(String type, String filter) {
this.type = type;
this.filter = filter;
}
}
}