/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.test.internals;
import org.apache.commons.io.FileUtils;
import org.junit.runners.model.InitializationError;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.ow2.chameleon.core.Chameleon;
import org.ow2.chameleon.core.ChameleonConfiguration;
import org.ow2.chameleon.testing.helpers.TimeUtils;
import org.slf4j.LoggerFactory;
import org.wisdom.maven.osgi.BundlePackager;
import org.wisdom.maven.osgi.Reporter;
import org.wisdom.maven.utils.ChameleonInstanceHolder;
import org.wisdom.test.shared.InVivoRunner;
import org.wisdom.test.shared.InVivoRunnerFactory;
import java.io.File;
import java.io.IOException;
/**
* Handles a Chameleon and manages the singleton instance.
*/
public final class ChameleonExecutor {
private static final String APPLICATION_BUNDLE = "target/osgi/application.jar";
private static ChameleonExecutor INSTANCE;
private File root;
private ChameleonExecutor() {
// Avoid direct instantiation.
}
/**
* Gets the instance of Chameleon, i.e. the OSGi Framework where the test are executed.
*
* @param root the base directory of the Chameleon.
* @return the Chameleon Executor instance, newly created if none, or reuses if any.
* @throws java.io.IOException if the chameleon configuration cannot be read.
* @throws org.osgi.framework.BundleException if the chameleon cannot be started.
*/
public static synchronized ChameleonExecutor instance(File root) throws BundleException, IOException {
if (INSTANCE == null) {
File application = new File(APPLICATION_BUNDLE);
if (application.isFile()) {
FileUtils.deleteQuietly(application);
}
INSTANCE = new ChameleonExecutor();
INSTANCE.root = root;
INSTANCE.start(root);
}
return INSTANCE;
}
/**
* Stops the running Chameleon.
*
* @throws Exception if the Chameleon instance cannot be stopped.
*/
public static synchronized void stopRunningInstance() throws Exception {
if (INSTANCE != null) {
INSTANCE.stop();
ChameleonInstanceHolder.set(null);
INSTANCE = null;
}
}
/**
* Starts the underlying Chameleon instance.
*
* @param root the base directory of the Chameleon.
* @throws java.io.IOException if the chameleon configuration cannot be read.
* @throws org.osgi.framework.BundleException if the chameleon cannot be started.
*/
private void start(File root) throws BundleException, IOException {
ChameleonConfiguration configuration = new ChameleonConfiguration(root);
// Use a different cache for testing.
configuration.put("org.osgi.framework.storage", root.getAbsolutePath() + "/chameleon-test-cache");
StringBuilder packages = new StringBuilder();
Packages.junit(packages);
Packages.wisdomtest(packages);
Packages.javaxinject(packages);
Packages.assertj(packages);
configuration.put("org.osgi.framework.system.packages.extra", packages.toString());
// Set the httpPort to 0 to use the random port feature.
// Except if already set explicitly
String port = System.getProperty("http.port");
if (port == null) {
System.setProperty("http.port", "0");
}
Chameleon chameleon = new Chameleon(configuration);
ChameleonInstanceHolder.fixLoggingSystem(root);
chameleon.start();
// Set the TIME_FACTOR
String factor = System.getProperty("time.factor");
if (factor != null) {
int factorAsInteger = getFactorAsAnInteger(factor);
if (factorAsInteger == 1) {
LoggerFactory.getLogger(this.getClass()).info("Setting time.factor to " + factorAsInteger + " (given " +
"as '" + factor + "')");
} else {
LoggerFactory.getLogger(this.getClass()).info("Setting time.factor to " + factorAsInteger);
}
setTimeFactory(factorAsInteger);
}
chameleon.waitForStability();
ChameleonInstanceHolder.set(chameleon);
}
private static void setTimeFactory(int factor) {
TimeUtils.TIME_FACTOR = factor;
}
private int getFactorAsAnInteger(String factor) {
try {
int f = Integer.valueOf(factor);
if (f <= 0) {
return 1;
} else {
return f;
}
} catch (NumberFormatException e) {
return 1;
}
}
/**
* @return the bundle context of the underlying Chameleon, {@literal null} if not started.
*/
public BundleContext context() {
return ChameleonInstanceHolder.get().context();
}
/**
* Waits for stability of the underlying frameworks.
*
* @return the current instance
*/
public ChameleonExecutor waitForStability() {
ChameleonInstanceHolder.get().waitForStability();
return this;
}
/**
* Stops the underlying chameleon instance.
* If we have an application artifact, restore it in the 'application' directory. It may have been deleted from
* the 'application' directory before running the integration tests.
*
* @throws Exception if it cannot be stopped.
*/
private void stop() throws Exception {
File original = RunnerUtils.getApplicationArtifactIfExists(root);
ChameleonInstanceHolder.get().stop();
// Restore the application bundle is any.
if (original != null) {
// We need to recompute the bundle name
String fileName = RunnerUtils.getBundleFileName();
File out = new File(new File(root, "application"), fileName);
FileUtils.copyFile(original, out, true);
}
}
/**
* Deploys the `probe` bundle, i.e. the bundle containing the test classes and the Wisdom Test Utilities (such as
* the InVivo Runner). If such a bundle is already deployed, nothing is done, else, the probe bundle is built,
* installed and started.
* <p>
* Initially, this method was returning {@code null}. In the 0.7 version, it changes to {@code Bundle}. The
* returned object is the installed bundle.
*
* @return the probe bundle.
* @throws BundleException if the probe bundle cannot be started.
*/
public static Bundle deployProbe() throws BundleException {
for (Bundle bundle : ChameleonInstanceHolder.get().context().getBundles()) {
if (bundle.getSymbolicName().equals(ProbeBundleMaker.BUNDLE_NAME)) {
return bundle;
}
}
try {
Bundle probe = ChameleonInstanceHolder.get().context().installBundle("local", ProbeBundleMaker.probe());
probe.start();
return probe;
} catch (Exception e) {
throw new RuntimeException("Cannot install or start the probe bundle", e);
}
}
/**
* Builds and deploy the application bundle.
* This method is called the application bundle is not in the runtime or application directories.
*/
public static void deployApplication() throws BundleException {
File application = new File(APPLICATION_BUNDLE);
File base = new File(".");
if (!application.isFile()) {
try {
BundlePackager.bundle(base, application, new Reporter() {
@Override
public void error(String msg) {
LoggerFactory.getLogger("Bundle Packager").error(msg);
}
@Override
public void warn(String msg) {
LoggerFactory.getLogger("Bundle Packager").warn(msg);
}
});
} catch (Exception e) {
throw new RuntimeException("Cannot build the application bundle", e);
}
}
try {
Bundle app = ChameleonInstanceHolder.get().context().installBundle(application.toURI().toURL().toExternalForm());
app.start();
} catch (Exception e) {
throw new RuntimeException("Cannot install or start the application bundle", e);
}
}
/**
* Retrieves the InVivoRunner Factory and creates an instance.
*
* @param clazz the test class
* @return the runner
*/
public InVivoRunner getInVivoRunnerInstance(Class<?> clazz) throws InitializationError, ClassNotFoundException, IOException {
ServiceReference<InVivoRunnerFactory> reference = context().getServiceReference(InVivoRunnerFactory.class);
if (reference == null) {
throw new IllegalStateException("Cannot retrieve the test probe from Wisdom Framework");
} else {
InVivoRunnerFactory factory = context().getService(reference);
return factory.create(clazz.getName());
}
}
}