/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.karaf.util.tracker;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BaseActivator implements BundleActivator, Runnable {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected BundleContext bundleContext;
protected ExecutorService executor = new ThreadPoolExecutor(0, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
private AtomicBoolean scheduled = new AtomicBoolean();
private long schedulerStopTimeout = TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS);
private final Queue<ServiceRegistration> registrations = new ConcurrentLinkedQueue<>();
private Map<String, SingleServiceTracker> trackers = new HashMap<>();
private ServiceRegistration managedServiceRegistration;
private Dictionary<String, ?> configuration;
public long getSchedulerStopTimeout() {
return schedulerStopTimeout;
}
public void setSchedulerStopTimeout(long schedulerStopTimeout) {
this.schedulerStopTimeout = schedulerStopTimeout;
}
@Override
public void start(BundleContext context) throws Exception {
bundleContext = context;
scheduled.set(true);
doOpen();
scheduled.set(false);
if (managedServiceRegistration == null
&& trackers.values().stream()
.allMatch(t -> t.getService() != null)) {
try {
doStart();
} catch (Exception e) {
logger.warn("Error starting activator", e);
doStop();
}
} else {
reconfigure();
}
}
@Override
public void stop(BundleContext context) throws Exception {
scheduled.set(true);
doClose();
executor.shutdown();
executor.awaitTermination(schedulerStopTimeout, TimeUnit.MILLISECONDS);
doStop();
}
protected void doOpen() throws Exception {
URL data = bundleContext.getBundle().getResource("OSGI-INF/karaf-tracker/" + getClass().getName());
if (data != null) {
Properties props = new Properties();
try (InputStream is = data.openStream()) {
props.load(is);
}
for (String key : props.stringPropertyNames()) {
if ("pid".equals(key)) {
manage(props.getProperty(key));
} else {
trackService(key, props.getProperty(key));
}
}
}
}
protected void doClose() {
if (managedServiceRegistration != null) {
managedServiceRegistration.unregister();
}
for (SingleServiceTracker tracker : trackers.values()) {
tracker.close();
}
}
protected void doStart() throws Exception {
}
protected void doStop() {
while (true) {
ServiceRegistration reg = registrations.poll();
if (reg == null) {
break;
}
reg.unregister();
}
}
/**
* Called in {@link #doOpen()}.
*
* @param pid The configuration PID to manage (ManagedService).
*/
protected void manage(String pid) {
Hashtable<String, Object> props = new Hashtable<>();
props.put(Constants.SERVICE_PID, pid);
managedServiceRegistration = bundleContext.registerService(
"org.osgi.service.cm.ManagedService", this, props);
}
public void updated(Dictionary<String, ?> properties) {
this.configuration = properties;
reconfigure();
}
protected Dictionary<String, ?> getConfiguration() {
return configuration;
}
/**
* Called in {@link #doStart()}.
*
* @param key The configuration key
* @param def The default value.
* @return The value of the configuration key if found, the default value else.
*/
protected int getInt(String key, int def) {
if (configuration != null) {
Object val = configuration.get(key);
if (val instanceof Number) {
return ((Number) val).intValue();
} else if (val != null) {
return Integer.parseInt(val.toString());
}
}
return def;
}
/**
* Called in {@link #doStart()}.
*
* @param key The configuration key.
* @param def The default value.
* @return The value of the configuration key if found, the default value else.
*/
protected boolean getBoolean(String key, boolean def) {
if (configuration != null) {
Object val = configuration.get(key);
if (val instanceof Boolean) {
return (Boolean) val;
} else if (val != null) {
return Boolean.parseBoolean(val.toString());
}
}
return def;
}
/**
* Called in {@link #doStart()}.
*
* @param key The configuration key.
* @param def The default value.
* @return The value of the configuration key if found, the default value else.
*/
protected long getLong(String key, long def) {
if (configuration != null) {
Object val = configuration.get(key);
if (val instanceof Number) {
return ((Number) val).longValue();
} else if (val != null) {
return Long.parseLong(val.toString());
}
}
return def;
}
/**
* Called in {@link #doStart()}.
*
* @param key The configuration key.
* @param def The default value.
* @return The value of the configuration key if found, the default value else.
*/
protected String getString(String key, String def) {
if (configuration != null) {
Object val = configuration.get(key);
if (val != null) {
return val.toString();
}
}
return def;
}
protected String[] getStringArray(String key, String def) {
Object val = null;
if (configuration != null) {
val = configuration.get(key);
}
if (val == null) {
val = def;
}
if (val == null) {
return null;
}
Stream<String> s;
if (val instanceof String[]) {
return (String[]) val;
} else if (val instanceof Iterable) {
return StreamSupport.stream(((Iterable<?>) val).spliterator(), false)
.map(Object::toString).toArray(String[]::new);
} else {
return val.toString().split(",");
}
}
protected void reconfigure() {
if (scheduled.compareAndSet(false, true)) {
executor.submit(this);
}
}
@Override
public void run() {
scheduled.set(false);
doStop();
try {
doStart();
} catch (Exception e) {
logger.warn("Error starting activator", e);
doStop();
}
}
/**
* Called in {@link #doOpen()}.
*
* @param clazz The service interface to track.
* @throws InvalidSyntaxException If the tracker syntax is not correct.
*/
protected void trackService(Class<?> clazz) throws InvalidSyntaxException {
if (!trackers.containsKey(clazz.getName())) {
SingleServiceTracker tracker = new SingleServiceTracker<>(bundleContext, clazz, (u, v) -> reconfigure());
tracker.open();
trackers.put(clazz.getName(), tracker);
}
}
/**
* Called in {@link #doOpen()}.
*
* @param clazz The service interface to track.
* @param filter The filter to use to select the services to track.
* @throws InvalidSyntaxException If the tracker syntax is not correct (in the filter especially).
*/
protected void trackService(Class<?> clazz, String filter) throws InvalidSyntaxException {
if (!trackers.containsKey(clazz.getName())) {
if (filter != null && filter.isEmpty()) {
filter = null;
}
SingleServiceTracker tracker = new SingleServiceTracker<>(bundleContext, clazz, filter, (u, v) -> reconfigure());
tracker.open();
trackers.put(clazz.getName(), tracker);
}
}
protected void trackService(String className, String filter) throws InvalidSyntaxException {
if (!trackers.containsKey(className)) {
SingleServiceTracker tracker = new SingleServiceTracker<>(bundleContext, className, filter, (u, v) -> reconfigure());
tracker.open();
trackers.put(className, tracker);
}
}
/**
* Called in {@link #doStart()}.
*
* @param clazz The service interface to get.
* @param <T> The service type.
* @return The actual tracker service object.
*/
protected <T> T getTrackedService(Class<T> clazz) {
SingleServiceTracker tracker = trackers.get(clazz.getName());
if (tracker == null) {
throw new IllegalStateException("Service not tracked for class " + clazz);
}
return clazz.cast(tracker.getService());
}
/**
* Called in {@link #doStart()}.
*
* @param mbean The MBean to register.
* @param type The MBean type to register.
*/
protected void registerMBean(Object mbean, String type) {
String name = "org.apache.karaf:" + type + ",name=" + System.getProperty("karaf.name");
registerMBeanWithName(mbean, name);
}
/**
* Called in {@link #doStart()}.
*
* @param mbean The MBean to register.
* @param name The MBean name.
*/
protected void registerMBeanWithName(Object mbean, String name) {
Hashtable<String, Object> props = new Hashtable<>();
props.put("jmx.objectname", name);
trackRegistration(bundleContext.registerService(getInterfaceNames(mbean), mbean, props));
}
/**
* Called in {@link #doStart()}.
*
* @param clazz The service interface to register.
* @param <T> The service type.
* @param service The actual service instance to register.
*/
protected <T> void register(Class<T> clazz, T service) {
register(clazz, service, null);
}
/**
* Called in {@link #doStart()}.
*
* @param clazz The service interface to register.
* @param <T> The service type.
* @param service The actual service instance to register.
* @param props The service properties to register.
*/
protected <T> void register(Class<T> clazz, T service, Dictionary<String, ?> props) {
trackRegistration(bundleContext.registerService(clazz, service, props));
}
/**
* Called in {@link #doStart()}.
*
* @param clazz The service interfaces to register.
* @param service The actual service instance to register.
*/
protected void register(Class[] clazz, Object service) {
register(clazz, service, null);
}
/**
* Called in {@link #doStart()}.
*
* @param clazz The service interfaces to register.
* @param service The actual service instance to register.
* @param props The service properties to register.
*/
protected void register(Class[] clazz, Object service, Dictionary<String, ?> props) {
String[] names = new String[clazz.length];
for (int i = 0; i < clazz.length; i++) {
names[i] = clazz[i].getName();
}
trackRegistration(bundleContext.registerService(names, service, props));
}
private void trackRegistration(ServiceRegistration registration) {
registrations.add(registration);
}
protected String[] getInterfaceNames(Object object) {
List<String> names = new ArrayList<>();
for (Class cl = object.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
addSuperInterfaces(names, cl);
}
return names.toArray(new String[names.size()]);
}
private void addSuperInterfaces(List<String> names, Class clazz) {
for (Class cl : clazz.getInterfaces()) {
names.add(cl.getName());
addSuperInterfaces(names, cl);
}
}
}