package org.jtheque.osgi.server;
import org.jtheque.utils.SystemProperty;
import org.jtheque.utils.collections.CollectionUtils;
import org.jtheque.utils.io.FileUtils;
import org.apache.felix.framework.Felix;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Map;
/*
* Copyright JTheque (Baptiste Wicht)
*
* 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.
*/
/**
* A Felix OSGi server implementation.
*
* @author Baptiste Wicht
*/
public final class FelixServer implements OSGiServer {
private static final File BUNDLES_DIR = new File(SystemProperty.USER_DIR.get(), "core/bundles");
private static final File CACHE_DIR = new File(SystemProperty.USER_DIR.get(), "core/cache");
private final Map<String, Bundle> bundles = CollectionUtils.newHashMap(50);
private Felix felix;
@Override
public void start() {
getLogger().debug("Starting Felix Server");
emptyFelixCache();
startFelixServer();
autoDeploy();
getLogger().info("Felix started with {} cached bundles", bundles.size());
}
/**
* Empty the cache.
*/
private void emptyFelixCache() {
long startTime = System.currentTimeMillis();
for (File f : CACHE_DIR.listFiles()) {
FileUtils.delete(f);
}
getLogger().debug("Cache cleaned in {} ms", System.currentTimeMillis() - startTime);
}
/**
* Start the Felix server.
*/
private void startFelixServer() {
long startTime = System.currentTimeMillis();
Map<String, Object> configMap = CollectionUtils.newHashMap(2);
configMap.put("felix.cache.bufsize", "16384");
configMap.put("org.osgi.framework.storage", CACHE_DIR.getAbsolutePath());
//configMap.put("org.osgi.framework.bootdelegation", "org.netbeans.lib.profiler, org.netbeans.lib.profiler.*");
try {
felix = new Felix(configMap);
felix.start();
} catch (Exception e) {
getLogger().error("Unable to start Felix due to {}", e.getMessage());
getLogger().error(e.getMessage(), e);
}
for (Bundle bundle : felix.getBundleContext().getBundles()) {
bundles.put(bundle.getSymbolicName(), bundle);
}
getLogger().debug("Felix Server started in {} ms", System.currentTimeMillis() - startTime);
}
/**
* Auto deploy the bundles in the "bundles" folder of the user dir.
*/
private void autoDeploy() {
long startTime = System.currentTimeMillis();
getLogger().info("Auto deploy start");
for (File f : BUNDLES_DIR.listFiles(new JarFileFilter())) {
String name = f.getName();
getLogger().info("auto-deploy {} file" + f.getName());
installIfNecessary(name.substring(0, name.indexOf('-')), f.getAbsolutePath());
}
getLogger().info("Auto deploy ends in {} ms", System.currentTimeMillis() - startTime);
}
@Override
public void stop() {
try {
stopBundles();
felix.stop();
felix.waitForStop(1000);
getLogger().info("Felix stopped");
} catch (BundleException e) {
getLogger().error("Unable to stop Felix due to {}", e.getMessage());
getLogger().error(e.getMessage(), e);
} catch (InterruptedException e) {
getLogger().error("Unable to stop Felix due to {}", e.getMessage());
getLogger().error(e.getMessage(), e);
}
}
/**
* Stop all the bundles other than the felix framework system bundle.
*/
private void stopBundles() {
try {
for (Bundle bundle : bundles.values()) {
if (!"org.apache.felix.framework".equals(bundle.getSymbolicName())) {
bundle.stop();
}
}
} catch (BundleException e) {
LoggerFactory.getLogger(getClass()).error("Cannot stop System Bundle");
LoggerFactory.getLogger(getClass()).error(e.getMessage(), e);
}
}
@Override
public Bundle[] getBundles() {
return felix.getBundleContext().getBundles();
}
@Override
public void installBundle(String path) {
try {
String url = path.startsWith("file:") ? path : "file:" + path;
Bundle bundle = felix.getBundleContext().installBundle(url);
bundles.put(bundle.getSymbolicName(), bundle);
getLogger().info("Installed bundle {}:{}", bundle.getSymbolicName(), bundle.getVersion());
} catch (BundleException e) {
getLogger().error("Unable to install bundle at path {} due to {}", path, e.getMessage());
getLogger().error(e.getMessage(), e);
}
}
@Override
public void startBundle(String bundleName) {
Bundle bundle = getBundle(bundleName);
if (bundle != null) {
try {
getLogger().debug("Start bundle {}", bundle.getSymbolicName());
bundle.start();
getLogger().debug("Started bundle {}", bundle.getSymbolicName());
} catch (BundleException e) {
getLogger().error("Unable to start bundle ({}) due to {}", bundleName, e.getMessage());
getLogger().error(e.getMessage(), e);
debug();
}
} else {
getLogger().error("Unable to start bundle ({}) because it's not installed", bundleName);
}
}
@Override
public void stopBundle(String bundleName) {
Bundle bundle = getBundle(bundleName);
if (bundle != null) {
try {
getLogger().debug("Stop bundle {}", bundle.getSymbolicName());
bundle.stop();
getLogger().info("Stopped bundle {}", bundle.getSymbolicName());
} catch (BundleException e) {
getLogger().error("Unable to stop bundle ({}) due to {}", bundleName, e.getMessage());
getLogger().error(e.getMessage(), e);
debug();
}
} else {
getLogger().error("Unable to stop bundle ({}) because it's not installed", bundleName);
}
}
@Override
public void uninstallBundle(String bundleName) {
Bundle bundle = getBundle(bundleName);
if (bundle != null) {
try {
getLogger().debug("Uninstall bundle {}", bundle.getSymbolicName());
bundle.uninstall();
getLogger().info("Uninstalled bundle {}", bundle.getSymbolicName());
} catch (BundleException e) {
getLogger().error("Unable to uninstall bundle ({}) due to {}", bundleName, e.getMessage());
getLogger().error(e.getMessage(), e);
}
} else {
getLogger().error("Unable to uninstall bundle ({}) because it's not installed", bundleName);
}
}
@Override
public Version getVersion(String bundleName) {
return getVersion(getBundle(bundleName));
}
@Override
public Version getVersion(Bundle bundle) {
if (bundle != null) {
return bundle.getVersion();
}
return null;
}
/**
* Debug the server. It seems print all the installed bundles with informations about them.
*/
private void debug() {
getLogger().debug("Installed bundles : ");
for (Bundle bundle : getBundles()) {
getLogger().debug("{} ({}) : {}", new Object[]{bundle.getSymbolicName(), getVersion(bundle).toString(), getState(bundle)});
getLogger().debug("Exported packages : {}", bundle.getHeaders().get("Export-Package"));
getLogger().debug("Imported packages : {}", bundle.getHeaders().get("Import-Package"));
getLogger().debug("Registered services: {}", Arrays.toString(bundle.getRegisteredServices()));
}
}
@Override
public BundleState getState(String bundle) {
if (!isInstalled(bundle)) {
return BundleState.NOT_INSTALLED;
}
return getState(getBundle(bundle));
}
@Override
public BundleState getState(Bundle bundle) {
int state = bundle.getState();
switch (state) {
case 1:
return BundleState.UNINSTALLED;
case 2:
return BundleState.INSTALLED;
case 4:
return BundleState.RESOLVED;
case 8:
return BundleState.STARTING;
case 16:
return BundleState.STOPPING;
case 32:
return BundleState.ACTIVE;
default:
getLogger().error("Undefined bundle state bundle : {}, state : {}", bundle.getSymbolicName(), state);
return null;
}
}
/**
* Install the module with the given name if it's not installed.
*
* @param name The module name.
* @param path The path to the module file.
*/
private void installIfNecessary(String name, String path) {
if (!isInstalled(name)) {
installBundle(path);
}
}
@Override
public boolean isInstalled(String bundleName) {
return getBundle(bundleName) != null;
}
@Override
public Bundle getBundle(String bundleName) {
return bundles.get(bundleName);
}
/**
* Return the logger of the server.
*
* @return The logger of the server.
*/
private Logger getLogger() {
return LoggerFactory.getLogger(getClass());
}
/**
* A file name filter to keep only JAR files.
*
* @author Baptiste Wicht
*/
private static final class JarFileFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
}
}