/*
* 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 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.startlevel.StartLevel;
import static org.ops4j.lang.NullArgumentException.*;
/**
* {@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
{
/**
* JCL Logger.
*/
private static final Log LOG = LogFactory.getLog( RemoteBundleContextImpl.class );
/**
* Bundle context (cannot be null).
*/
private final transient BundleContext m_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" );
m_bundleContext = bundleContext;
}
/**
* {@inheritDoc}
*/
public Object remoteCall( final Class<?> serviceType,
final String methodName,
final Class<?>[] methodParams,
final long timeoutInMillis,
final Object... actualParams )
throws NoSuchServiceException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
LOG.info( "Remote call of [" + serviceType.getName() + "." + methodName + "]" );
return serviceType.getMethod( methodName, methodParams ).invoke(
getService( serviceType, timeoutInMillis ),
actualParams
);
}
/**
* {@inheritDoc}
*/
public long installBundle( final String bundleUrl )
throws BundleException
{
LOG.info( "Install bundle from URL [" + bundleUrl + "]" );
return m_bundleContext.installBundle( bundleUrl ).getBundleId();
}
/**
* {@inheritDoc}
*/
public long installBundle( final String bundleLocation,
final byte[] bundle )
throws BundleException
{
LOG.info( "Install bundle [" + bundleLocation + "] from byte array" );
final ByteArrayInputStream inp = new ByteArrayInputStream( bundle );
try
{
return m_bundleContext.installBundle( bundleLocation, inp ).getBundleId();
}
finally
{
try
{
inp.close();
}
catch( IOException e )
{
// ignore.
}
}
}
/**
* {@inheritDoc}
*/
public void startBundle( long bundleId )
throws BundleException
{
startBundle( m_bundleContext.getBundle( bundleId ) );
}
/**
* {@inheritDoc}
*/
public void stopBundle( long bundleId )
throws BundleException
{
m_bundleContext.getBundle( bundleId ).stop();
}
/**
* {@inheritDoc}
*/
public void setBundleStartLevel( long bundleId, int startLevel )
throws RemoteException, BundleException
{
try
{
final StartLevel startLevelService = getService( StartLevel.class, 0 );
startLevelService.setBundleStartLevel( m_bundleContext.getBundle( bundleId ), startLevel );
}
catch( NoSuchServiceException e )
{
throw new BundleException( "Cannot get the start level service to set bundle start level" );
}
}
/**
* {@inheritDoc}
*/
public void waitForState( final long bundleId,
final int state,
final long timeoutInMillis )
throws TimeoutException
{
Bundle bundle = m_bundleContext.getBundle( bundleId );
if( timeoutInMillis == NO_WAIT && (bundle == null || bundle.getState() < state ))
{
throw new TimeoutException(
"There is no waiting timeout set and bundle has state '" + bundleStateToString( bundle.getState() )
+ "' not '" + bundleStateToString( state ) + "' as expected"
);
}
long startedTrying = System.currentTimeMillis();
do
{
bundle = m_bundleContext.getBundle( bundleId );
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
break;
}
}
while( (bundle == null || bundle.getState() < state)
&& ( timeoutInMillis == WAIT_FOREVER
|| System.currentTimeMillis() < startedTrying + timeoutInMillis ) );
// bundle != null && bundle.getState() >= state
// or
// timeoutInMillis != WAIT_FOREVER && System.currentTimeMillis() >= startedTrying + timeoutInMillis
if( bundle == null || bundle.getState() < state )
{
throw new TimeoutException(
"Timeout passed and bundle has state '" + bundleStateToString( bundle.getState() )
+ "' not '" + bundleStateToString( state ) + "' as expected"
);
}
}
/**
* Lookup a service in the service registry.
*
* @param serviceType service class
* @param timeoutInMillis number of milliseconds to wait for service before failing
* TODO timeout is not used!
*
* @return a service published under the required service type
*
* @throws NoSuchServiceException - If service cannot be found in the service registry
*/
private <T> T getService( final Class<T> serviceType,
final long timeoutInMillis )
throws NoSuchServiceException
{
LOG.info( "Look up service [" + serviceType.getName() + "], timeout in " + timeoutInMillis + " millis" );
final ServiceReference ref = m_bundleContext.getServiceReference( serviceType.getName() );
if( ref != null )
{
final Object service = m_bundleContext.getService( ref );
if( service == null )
{
throw new NoSuchServiceException( serviceType );
}
return (T) service;
}
else
{
throw new NoSuchServiceException( serviceType );
}
}
/**
* 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();
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 bundleState bundle state
*
* @return bundle state as string
*/
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 + ")";
}
}
}