/*******************************************************************************
* Copyright (c) 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Jochen Hiller
*******************************************************************************/
package org.eclipse.concierge.test.util;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.is;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.concierge.Concierge;
import org.eclipse.concierge.Factory;
import org.junit.Assert;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.FrameworkWiring;
/**
* @author Jochen Hiller
*/
public abstract class AbstractConciergeTestCase {
/**
* This property allows to wait some time when framework has been shutdown.
*/
private final static String PROPERTY_WAIT_AFTER_FRAMEWORK_SHUTDOWN = "org.eclipse.concierge.tests.waitAfterFrameworkShutdown";
protected Framework framework = null;
protected BundleContext bundleContext = null;
protected LocalBundleStorage localBundleStorage = LocalBundleStorage
.getInstance();
/** Start framework with default settings, which cleans storage first. */
public void startFramework() throws Exception {
final Map<String, String> launchArgs = new HashMap<String, String>();
// start OSGi framework in clean mode as default
startFrameworkClean(launchArgs);
}
/** Start framework in NON clean mode. */
public void startFrameworkNonClean() throws Exception {
Map<String, String> launchArgs = new HashMap<String, String>();
startFramework(launchArgs);
}
/** Start framework with given settings but in clean mode. */
public void startFrameworkClean(Map<String, String> launchArgs)
throws Exception {
launchArgs.put("org.eclipse.concierge.debug", "true");
launchArgs.put("org.osgi.framework.storage.clean", "onFirstInit");
startFramework(launchArgs);
}
/** Start framework with given settings. */
public void startFramework(final Map<String, String> launchArgs)
throws Exception {
Framework frameworkToStart = new Factory().newFramework(launchArgs);
frameworkToStart.init();
frameworkToStart.start();
useFramework(frameworkToStart);
}
/** Start framework for a given framework. */
public void useFramework(final Framework frameworkToStart)
throws Exception {
// start OSGi framework
this.framework = frameworkToStart;
this.bundleContext = this.framework.getBundleContext();
if (stayInShell()) {
String shellJarName = "./test/resources/org.eclipse.concierge.shell-5.0.0.20151029184259.jar";
if (!new File(shellJarName).exists()) {
System.err.println(
"Oops, could not find shell bundle at " + shellJarName);
} else {
// assume to get shell jar file in target folder
installAndStartBundle(shellJarName);
}
}
}
public void stopFramework() throws Exception {
// it may happen that framework has not been set
if (this.framework != null) {
if (stayInShell()) {
this.framework.waitForStop(0);
} else {
this.framework.stop();
FrameworkEvent event = framework.waitForStop(10000);
Assert.assertThat(FrameworkEvent.STOPPED, is(event.getType()));
// force a GC to allow cleanup of files
// on Mac from time to time files from storage can not be
// deleted
// until a GC has been run
System.gc();
// We have from time to time problems when shutdown the
// framework, that next tests are failing
// for CI build we can define a timeout to wait here. A good
// value is 100ms
String propValue = System
.getProperty(PROPERTY_WAIT_AFTER_FRAMEWORK_SHUTDOWN);
int timeout = -1;
if ((propValue != null) && (propValue.length() > 0)) {
try {
timeout = Integer.valueOf(propValue);
} catch (NumberFormatException ex) {
// ignore
}
if (timeout > 0) {
Thread.sleep(timeout);
}
}
}
}
}
/**
* stop the framework, but wait until it will be stopped by someone. Use
* this if framework started, but will be closed interactively.
*/
public void stopFrameworkWaitForStop() throws Exception {
if (this.framework != null) {
this.framework.waitForStop(0);
}
}
// Utilities
/**
* Gets a framework property by casting into implementation.
*/
protected String getFrameworkProperty(String propertyName) {
try {
final Concierge c = (Concierge) this.framework;
final Field f = c.getClass().getDeclaredField("properties");
f.setAccessible(true);
Object o = f.get(c);
Properties p = (Properties) o;
String s = (String) p.get(propertyName);
return s;
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* Override when a test case should use shell and wait for manual exit of
* framework.
*/
protected boolean stayInShell() {
return false;
}
protected Bundle[] installBundles(final String[] bundleNames)
throws BundleException {
final Bundle[] bundles = new Bundle[bundleNames.length];
for (int i = 0; i < bundleNames.length; i++) {
final String url = this.localBundleStorage
.getUrlForBundle(bundleNames[i]);
bundles[i] = bundleContext.installBundle(url);
}
return bundles;
}
protected void startBundles(Bundle[] bundles) throws BundleException {
for (int i = 0; i < bundles.length; i++) {
if (!isFragmentBundle(bundles[i])) {
bundles[i].start();
}
}
}
protected Bundle[] installAndStartBundles(final String[] bundleNames)
throws BundleException {
final Bundle[] bundles = new Bundle[bundleNames.length];
for (int i = 0; i < bundleNames.length; i++) {
bundles[i] = installAndStartBundle(bundleNames[i]);
}
return bundles;
}
/**
* Install a bundle for given name.
*/
protected Bundle installBundle(final String bundleName)
throws BundleException {
final String url = this.localBundleStorage.getUrlForBundle(bundleName);
final Bundle bundle = bundleContext.installBundle(url);
return bundle;
}
/**
* Install a bundle for given name. Will check whether bundle can be
* resolved.
*/
protected Bundle installAndStartBundle(final String bundleName)
throws BundleException {
final String url = this.localBundleStorage.getUrlForBundle(bundleName);
// System.err.println("installAndStartBundle: " + bundleName);
final Bundle bundle = bundleContext.installBundle(url);
if (!isFragmentBundle(bundle)) {
bundle.start();
}
return bundle;
}
/**
* Enforce to call resolve bundle in Concierge framework for the specified
* bundle.
*/
protected void enforceResolveBundle(final Bundle bundle) {
// initiate resolver
framework.adapt(FrameworkWiring.class)
.resolveBundles(Collections.singleton(bundle));
}
/** Returns true when the specified bundle is a fragment. */
protected boolean isFragmentBundle(final Bundle bundle) {
return (bundle.adapt(BundleRevision.class).getTypes()
& BundleRevision.TYPE_FRAGMENT) != 0;
}
/** Checks about Bundle RESOLVED state for all bundles. */
protected void assertBundlesResolved(final Bundle[] bundles) {
for (int i = 0; i < bundles.length; i++) {
assertBundleResolved(bundles[i]);
}
}
/** Checks about Bundle ACTIVE state for all bundles. */
protected void assertBundlesActive(final Bundle[] bundles) {
for (int i = 0; i < bundles.length; i++) {
assertBundleActive(bundles[i]);
}
}
/** Checks about Bundle RESOLVED or ACTIVE state. */
protected void assertBundleResolved(final Bundle bundle) {
if (isBundleResolved(bundle)) {
// all fine
} else {
Assert.assertThat(
"Bundle " + bundle.getSymbolicName()
+ " needs to be RESOLVED or ACTIVE",
getBundleStateAsString(bundle.getState()),
anyOf(is(getBundleStateAsString(Bundle.RESOLVED)),
is(getBundleStateAsString(Bundle.ACTIVE))));
}
}
/** Checks about Bundle RESOLVED or ACTIVE state. */
protected boolean isBundleResolved(final Bundle bundle) {
return ((bundle.getState() == Bundle.RESOLVED)
|| (bundle.getState() == Bundle.ACTIVE));
}
/** Checks about Bundle ACTIVE state. */
protected void assertBundleActive(final Bundle bundle) {
if (bundle.getState() == Bundle.ACTIVE) {
// all fine
} else {
if (isFragmentBundle(bundle) && isBundleResolved(bundle)) {
// all fine
} else {
Assert.assertThat(
"Bundle " + bundle.getSymbolicName()
+ " needs to be ACTIVE",
getBundleStateAsString(bundle.getState()),
is(getBundleStateAsString(Bundle.ACTIVE)));
}
}
}
/** Checks about Bundle INSTALLED state. */
protected void assertBundleInstalled(final Bundle bundle) {
if (bundle.getState() == Bundle.INSTALLED) {
// all fine
} else {
Assert.assertThat(
"Bundle " + bundle.getSymbolicName()
+ " needs to be INSTALLED",
getBundleStateAsString(bundle.getState()),
is(getBundleStateAsString(Bundle.INSTALLED)));
}
}
/**
* This method will install a "pseudo" bundle into the framework.
*/
protected Bundle installBundle(SyntheticBundleBuilder builder)
throws BundleException {
Bundle b = this.installBundle(builder.getBundleSymbolicName(),
builder.asInputStream());
return b;
}
protected Bundle installBundle(String bundleName, InputStream is)
throws BundleException {
final Bundle b = bundleContext.installBundle(bundleName, is);
return b;
}
/** Get bundle with given symbolic name from a list of bundles. */
protected Bundle getBundleForBSN(Bundle[] bundles, String bsn) {
for (int i = 0; i < bundles.length; i++) {
if (bundles[i].getSymbolicName().equals(bsn)) {
return bundles[i];
}
}
return null;
}
/** Returns bundle state as readable string. */
protected String getBundleStateAsString(final int state) {
switch (state) {
case Bundle.INSTALLED:
return "INSTALLED";
case Bundle.RESOLVED:
return "RESOLVED";
case Bundle.ACTIVE:
return "ACTIVE";
case Bundle.STARTING:
return "STARTING";
case Bundle.STOPPING:
return "STOPPING";
case Bundle.UNINSTALLED:
return "UNINSTALLED";
default:
return "UNKNOWN state: " + state;
}
}
protected void dumpStorage() throws Exception {
Concierge concierge = (Concierge) framework;
Field field = concierge.getClass().getDeclaredField("STORAGE_LOCATION");
field.setAccessible(true);
Object o = field.get(concierge);
System.err.println("dumpStorage: STORAGE_LOCATION=" + o);
File dir = new File((String) o);
dumpStorageDirectory(dir);
}
private static void dumpStorageDirectory(File path) {
File[] files = path.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
dumpStorageDirectory(files[i]);
} else {
System.err.println("dumpStorage: " + files[i]);
}
}
System.err.println("dumpStorage: " + path);
}
/**
* The <code>RunInClassLoader</code> class helps to run code in ClassLoader
* of the bundle using Java reflection. This allows to call testing code in
* context of a bundle without need to have classes and/or compile code for
* testing purposes.
*/
public static class RunInClassLoader {
private final Bundle bundle;
private boolean debug = false;
public RunInClassLoader(Bundle b) {
this.bundle = b;
}
public void debug(boolean debug) {
this.debug = debug;
}
/**
* Get a class references based on bundle classloader.
*/
public Class<?> getClass(final String className) throws Exception {
final Class<?> clazz = this.bundle.loadClass(className);
return clazz;
}
public Object getService(final String className) {
Object service = null;
ServiceReference<?> sref = this.bundle.getBundleContext()
.getServiceReference(className);
service = this.bundle.getBundleContext().getService(sref);
return service;
}
public Object getClassField(final String className,
final String classFieldName) throws Exception {
final Class<?> clazz = this.bundle.loadClass(className);
final Field field = clazz.getField(classFieldName);
if (!Modifier.isStatic(field.getModifiers())) {
throw new RuntimeException(
"Oops, field " + field.toString() + " is not static");
}
// get the value of field, as class field object == null
final Object result = field.get(null);
return result;
}
/**
* Call a class method for given class name. The method will be detected
* based on arguments.
*/
public Object callClassMethod(final String className,
final String classMethodName, final Object[] args)
throws Exception {
final Class<?> clazz = this.bundle.loadClass(className);
// get parameter types from args
final Class<?>[] parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
parameterTypes[i] = args[i].getClass();
}
final Method method = clazz.getDeclaredMethod(classMethodName,
parameterTypes);
if (!Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(
"Oops, method " + method.toString() + " is not static");
}
// TODO jhi Maybe set accessible if private?
final Object result = method.invoke(null, args);
return result;
}
public Object createInstance(String className, final Object[] args)
throws Exception {
final Class<?> clazz = this.bundle.loadClass(className);
dumpDeclaredConstructors(clazz);
// get parameter types from args
final Class<?>[] parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
parameterTypes[i] = Object.class;
} else {
parameterTypes[i] = args[i].getClass();
}
}
final Constructor<?> constructor = clazz
.getDeclaredConstructor(parameterTypes);
// TODO jhi Maybe set accessible if private?
final Object result = constructor.newInstance(args);
return result;
}
public Object createInstance(String className,
final String[] parameterTypeNames, final Object[] args)
throws Exception {
final Class<?> clazz = this.bundle.loadClass(className);
dumpDeclaredConstructors(clazz);
// get parameter types from args
final Class<?>[] parameterTypes = new Class[args.length];
for (int i = 0; i < parameterTypeNames.length; i++) {
parameterTypes[i] = bundle.getClass().getClassLoader()
.loadClass(parameterTypeNames[i]);
}
final Constructor<?> constructor = clazz
.getDeclaredConstructor(parameterTypes);
// TODO jhi Maybe set accessible if private?
final Object result = constructor.newInstance(args);
return result;
}
/**
* Call an instance method for given object. The method will be detected
* based on types of arguments.
*/
public Object callMethod(final Object obj, final String methodName,
final Object[] args) throws Exception {
final Class<?> clazz = obj.getClass();
final Class<?>[] parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] == null) {
parameterTypes[i] = Object.class;
} else {
parameterTypes[i] = args[i].getClass();
}
}
dumpMethods(clazz);
dumpDeclaredMethods(clazz);
final Method method = clazz.getMethod(methodName, parameterTypes);
if (Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(
"Oops, method " + method.toString() + " is static");
}
final Object result = method.invoke(obj, args);
return result;
}
/**
* Call an instance method for given object. The method will be detected
* based on give parameter types. Needed when args have to precise
* argument types, e.g. a method is of type Object, but the args is of
* type SomeClass.
*/
public Object callMethod(final Object obj, final String methodName,
final Class<?>[] parameterTypes, final Object[] args)
throws Exception {
final Class<?> clazz = obj.getClass();
dumpMethods(clazz);
dumpDeclaredMethods(clazz);
final Method method = clazz.getMethod(methodName, parameterTypes);
if (Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(
"Oops, method " + method.toString() + " is static");
}
final Object result = method.invoke(obj, args);
return result;
}
private void dumpMethods(Class<?> clazz) {
if (!debug) {
return;
}
System.out.println("dumpMethods: " + clazz.getName());
final Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
System.out.println("==================");
}
private void dumpDeclaredMethods(Class<?> clazz) {
if (!debug) {
return;
}
System.out.println("dumpDeclaredMethods: " + clazz.getName());
final Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
System.out.println("==================");
}
private void dumpDeclaredConstructors(Class<?> clazz) {
if (!debug) {
return;
}
System.out.println("dumpDeclaredConstructors: " + clazz.getName());
final Constructor<?>[] constructors = clazz
.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}
System.out.println("==================");
}
}
}