/*
* Copyright 2008 Toni Menzel
*
* 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.
*/
package org.ops4j.pax.exam.rbc.internal;
import static org.ops4j.lang.NullArgumentException.validateNotNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.rmi.RemoteException;
import java.util.Dictionary;
import org.ops4j.pax.exam.RelativeTimeout;
import org.ops4j.pax.exam.RerunTestException;
import org.ops4j.pax.exam.TimeoutException;
import org.ops4j.pax.swissbox.tracker.ServiceLookup;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link RemoteBundleContext} implementaton.
*
* @author Toni Menzel (tonit)
* @author Alin Dreghiciu (adreghiciu@gmail.com)
* @since 0.1.0, June 10, 2008
*/
public class RemoteBundleContextImpl implements RemoteBundleContext, Serializable {
private static final long serialVersionUID = 2520051681589147139L;
/**
* JCL Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(RemoteBundleContextImpl.class);
/**
* Bundle context (cannot be null).
*/
private final transient BundleContext bundleContext;
/**
* Constructor.
*
* @param bundleContext
* bundle context (cannot be null)
*
* @throws IllegalArgumentException
* - If bundle context is null
*/
public RemoteBundleContextImpl(final BundleContext bundleContext) {
validateNotNull(bundleContext, "Bundle context");
this.bundleContext = bundleContext;
}
public Object remoteCall(final Class<?> serviceType, final String methodName,
final Class<?>[] methodParams, String filter, final RelativeTimeout timeout,
final Object... actualParams) throws NoSuchServiceException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
LOG.trace("Remote call of [" + serviceType.getName() + "." + methodName + "]");
Object service = ServiceLookup.getService(bundleContext, serviceType, timeout.getValue(), filter);
Object obj = null;
try {
obj = serviceType.getMethod(methodName, methodParams).invoke(service, actualParams);
} catch (InvocationTargetException t) {
if (t.getTargetException().getCause() instanceof RerunTestException) {
LOG.debug("rerun the test");
service = ServiceLookup.getService(bundleContext, serviceType, timeout.getValue(), filter);
obj = serviceType.getMethod(methodName, methodParams).invoke(service, actualParams);
} else {
throw t;
}
}
return obj;
}
public long installBundle(final String bundleUrl) throws BundleException {
LOG.trace("Install bundle from URL [" + bundleUrl + "]");
return bundleContext.installBundle(bundleUrl).getBundleId();
}
public long installBundle(final String bundleLocation, final byte[] bundle)
throws BundleException {
LOG.trace("Install bundle [ location=" + bundleLocation + "] from byte array");
final ByteArrayInputStream inp = new ByteArrayInputStream(bundle);
try {
return bundleContext.installBundle(bundleLocation, inp).getBundleId();
}
finally {
try {
inp.close();
}
catch (IOException e) {
// ignore.
}
}
}
public void uninstallBundle(long id) throws BundleException {
LOG.trace("Uninstall bundle [" + id + "] ");
try {
bundleContext.getBundle(id).uninstall();
}
catch (BundleException e) {
LOG.error("Problem uninstalling " + id, e);
}
}
public void startBundle(long bundleId) throws BundleException {
startBundle(bundleContext.getBundle(bundleId));
}
public void stopBundle(long bundleId) throws BundleException {
bundleContext.getBundle(bundleId).stop();
}
public void setBundleStartLevel(long bundleId, int startLevel) throws RemoteException,
BundleException {
Bundle bundle = bundleContext.getBundle(bundleId);
bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
}
public void waitForState(final long bundleId, final int state, final RelativeTimeout timeout) {
Bundle bundle = bundleContext.getBundle(bundleId);
if (bundle == null || (timeout.isNoWait() && (bundle == null || bundle.getState() < state))) {
throw new TimeoutException("There is no waiting timeout set and bundle has state '"
+ bundleStateToString(bundle) + "' not '" + bundleStateToString(state)
+ "' as expected");
}
long startedTrying = System.currentTimeMillis();
do {
bundle = bundleContext.getBundle(bundleId);
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
while ((bundle == null || bundle.getState() < state)
&& (timeout.isNoTimeout() || System.currentTimeMillis() < startedTrying
+ timeout.getValue()));
if (bundle == null || bundle.getState() < state) {
throw new TimeoutException("Timeout passed and bundle has state '"
+ bundleStateToString(bundle.getState()) + "' not '" + bundleStateToString(state)
+ "' as expected");
}
}
/**
* Starts a bundle.
*
* @param bundle
* bundle to be started
*
* @throws BundleException
* - If bundle cannot be started
*/
private void startBundle(final Bundle bundle) throws BundleException {
// Don't start if bundle already active
int bundleState = bundle.getState();
if (bundleState == Bundle.ACTIVE) {
return;
}
// Don't start if bundle is a fragment bundle
Dictionary<?, ?> bundleHeaders = bundle.getHeaders();
if (bundleHeaders.get(Constants.FRAGMENT_HOST) != null) {
return;
}
// Start bundle
bundle.start();
waitForState(bundle.getBundleId(), Bundle.ACTIVE, RelativeTimeout.TIMEOUT_DEFAULT);
bundleState = bundle.getState();
if (bundleState != Bundle.ACTIVE) {
long bundleId = bundle.getBundleId();
String bundleName = bundle.getSymbolicName();
String bundleStateStr = bundleStateToString(bundleState);
throw new BundleException("Bundle (" + bundleId + ", " + bundleName
+ ") not started (still " + bundleStateStr + ")");
}
}
/**
* Coverts a bundle state to its string form.
*
* @param bundle
* bundle
*
* @return bundle state as string
*/
private static String bundleStateToString(Bundle bundle) {
if (bundle == null) {
return "not installed";
}
else {
return bundleStateToString(bundle.getState());
}
}
private static String bundleStateToString(int bundleState) {
switch (bundleState) {
case Bundle.ACTIVE:
return "active";
case Bundle.INSTALLED:
return "installed";
case Bundle.RESOLVED:
return "resolved";
case Bundle.STARTING:
return "starting";
case Bundle.STOPPING:
return "stopping";
case Bundle.UNINSTALLED:
return "uninstalled";
default:
return "unknown (" + bundleState + ")";
}
}
}