/*
* Copyright 2008 Alin Dreghiciu.
*
* 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.client;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.ConnectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleException;
import org.ops4j.pax.exam.Constants;
import org.ops4j.pax.exam.rbc.internal.RemoteBundleContext;
import org.ops4j.pax.exam.spi.container.TestContainer;
import org.ops4j.pax.exam.spi.container.TestContainerException;
import org.ops4j.pax.exam.spi.container.TimeoutException;
/**
* A {@link RemoteBundleContext} client, that takes away RMI handling.
*
* @author Alin Dreghiciu (adreghiciu@gmail.com)
* @since 0.3.0, December 15, 2008
*/
public class RemoteBundleContextClient
implements TestContainer
{
/**
* JCL logger.
*/
private static final Log LOG = LogFactory.getLog( RemoteBundleContextClient.class );
/**
* RMI communication port.
*/
private final Integer m_rmiPort;
/**
* Timeout for looking up the remote bundle context via RMI.
*/
private final long m_rmiLookupTimeout;
/**
* Remote bundle context instance.
*/
private RemoteBundleContext m_remoteBundleContext;
/**
* Constructor.
*
* @param rmiPort RMI communication port (cannot be null)
* @param rmiLookupTimeout timeout for looking up the remote bundle context via RMI (cannot be null)
*/
public RemoteBundleContextClient( final Integer rmiPort,
final long rmiLookupTimeout )
{
m_rmiPort = rmiPort;
m_rmiLookupTimeout = rmiLookupTimeout;
}
/**
* {@inheritDoc}
*/
public <T> T getService( final Class<T> serviceType )
{
return getService( serviceType, Constants.NO_WAIT );
}
/**
* {@inheritDoc}
* Returns a dynamic proxy in place of the actual service, forwarding the calls via the remote bundle context.
*/
@SuppressWarnings( "unchecked" )
public <T> T getService( final Class<T> serviceType,
final long timeoutInMillis )
{
return (T) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[]{ serviceType },
new InvocationHandler()
{
/**
* {@inheritDoc}
* Delegates the call to remote bundle context.
*/
public Object invoke( final Object proxy,
final Method method,
final Object[] params )
throws Throwable
{
try
{
return getRemoteBundleContext().remoteCall(
method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
timeoutInMillis,
params
);
}
catch( InvocationTargetException e )
{
throw e.getCause();
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( Exception e )
{
throw new TestContainerException( "Invocation exception", e );
}
}
}
);
}
/**
* {@inheritDoc}
*/
public long installBundle( final String bundleUrl )
{
try
{
return getRemoteBundleContext().installBundle( bundleUrl );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "Bundle cannot be installed", e );
}
}
/**
* {@inheritDoc}
*/
public long installBundle( final String bundleLocation,
final byte[] bundle )
throws TestContainerException
{
try
{
return getRemoteBundleContext().installBundle( bundleLocation, bundle );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "Bundle cannot be installed", e );
}
}
/**
* {@inheritDoc}
*/
public void startBundle( final long bundleId )
throws TestContainerException
{
try
{
getRemoteBundleContext().startBundle( bundleId );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "Bundle cannot be started", e );
}
}
/**
* {@inheritDoc}
*/
public void setBundleStartLevel( final long bundleId,
final int startLevel )
throws TestContainerException
{
try
{
getRemoteBundleContext().setBundleStartLevel( bundleId, startLevel );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "Start level cannot be set", e );
}
}
/**
* {@inheritDoc}
*/
public void start()
{
try
{
getRemoteBundleContext().startBundle( 0 );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "System bundle cannot be started", e );
}
}
/**
* {@inheritDoc}
*/
public void stop()
{
try
{
getRemoteBundleContext().stopBundle( 0 );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "System bundle cannot be stopped", e );
}
}
/**
* {@inheritDoc}
*/
public void waitForState( final long bundleId,
final int state,
final long timeoutInMillis )
throws TimeoutException
{
try
{
getRemoteBundleContext().waitForState( bundleId, state, timeoutInMillis );
}
catch( org.ops4j.pax.exam.rbc.internal.TimeoutException e )
{
throw new TimeoutException( e.getMessage() );
}
catch( RemoteException e )
{
throw new TestContainerException( "Remote exception", e );
}
catch( BundleException e )
{
throw new TestContainerException( "Bundle cannot be found", e );
}
}
/**
* Looks up the {@link RemoteBundleContext} via RMI. The lookup will timeout in the specified number of millis.
*
* @return remote bundle context
*/
private RemoteBundleContext getRemoteBundleContext()
{
if( m_remoteBundleContext == null )
{
long startedTrying = System.currentTimeMillis();
//!! Absolutely necesary for RMI class loading to work
// TODO maybe use ContextClassLoaderUtils.doWithClassLoader
Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
Throwable reason = null;
try
{
final Registry registry = LocateRegistry.getRegistry( m_rmiPort );
do
{
try
{
m_remoteBundleContext =
(RemoteBundleContext) registry.lookup( RemoteBundleContext.class.getName() );
}
catch( ConnectException e )
{
reason = e;
}
catch( NotBoundException e )
{
reason = e;
}
}
while( m_remoteBundleContext == null
&& ( m_rmiLookupTimeout == Constants.WAIT_FOREVER
|| System.currentTimeMillis() < startedTrying + m_rmiLookupTimeout ) );
}
catch( RemoteException e )
{
reason = e;
}
if( m_remoteBundleContext == null )
{
throw new TestContainerException( "Cannot get the remote bundle context", reason );
}
LOG.info(
"Remote bundle context found after " + ( System.currentTimeMillis() - startedTrying ) + " millis"
);
}
return m_remoteBundleContext;
}
/**
* Getter.
*
* @return rmi port
*/
public Integer getRmiPort()
{
return m_rmiPort;
}
}