/* * Copyright 2011 Harald Wellmann. * * 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.swissbox.framework; import java.io.ByteArrayInputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.rmi.AccessException; import java.rmi.AlreadyBoundException; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; 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.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.osgi.service.startlevel.StartLevel; import org.osgi.util.tracker.ServiceTracker; /** * Implements the {@link RemoteFramework} interface by instantiating a local {@link Framework}, * exporting it via an RMI registry and delegating all remote calls to the local framework. * * @author Harald Wellmann */ public class RemoteFrameworkImpl implements RemoteFramework { /* * The use of java.util.logging instead of SLF4J is intentional * to simplify classpath setup. */ private static Logger LOG = Logger.getLogger( RemoteFrameworkImpl.class.getName() ); private Framework framework; private Registry registry; private String name; private long timeout; public RemoteFrameworkImpl( Map<String, String> frameworkProperties ) throws RemoteException, AlreadyBoundException, BundleException { FrameworkFactory frameworkFactory = findFrameworkFactory(); this.framework = frameworkFactory.newFramework( frameworkProperties ); export(); } private void export() throws RemoteException, AccessException { String port = System.getProperty( RMI_PORT_KEY, "1099" ); name = System.getProperty( RMI_NAME_KEY ); timeout = Long.parseLong( System.getProperty( TIMEOUT_KEY, "10000") ); registry = LocateRegistry.getRegistry( Integer.parseInt( port ) ); URL location1 = getClass().getProtectionDomain().getCodeSource().getLocation(); URL location2 = Bundle.class.getProtectionDomain().getCodeSource().getLocation(); URL location3 = ServiceLookup.class.getProtectionDomain().getCodeSource().getLocation(); System.setProperty( "java.rmi.server.codebase", location1 + " " + location2 + " " + location3 ); Remote remote = UnicastRemoteObject.exportObject( this, 0 ); registry.rebind( name, remote ); } public void init() throws RemoteException, BundleException { framework.init(); } public void start() throws RemoteException, BundleException { framework.start(); } public void stop() throws RemoteException, BundleException { framework.stop(); try { framework.waitForStop(timeout); } catch (InterruptedException exc) { LOG.severe("framework did not stop within timeout"); } try { registry.unbind( name ); } catch ( NotBoundException exc ) { throw new IllegalStateException( exc ); } UnicastRemoteObject.unexportObject( this, true ); } public long installBundle( String bundleUrl ) throws RemoteException, BundleException { Bundle bundle = framework.getBundleContext().installBundle( bundleUrl ); return bundle.getBundleId(); } public long installBundle( String bundleUrl, boolean start, int startLevel ) throws RemoteException, BundleException { BundleContext bundleContext = framework.getBundleContext(); Bundle bundle = bundleContext.installBundle( bundleUrl ); setupBundle( start, startLevel, bundleContext, bundle ); return bundle.getBundleId(); } public long installBundle( String bundleLocation, byte[] bundleData, boolean start, int startLevel ) throws RemoteException, BundleException { BundleContext bundleContext = framework.getBundleContext(); Bundle bundle = bundleContext.installBundle( bundleLocation, new ByteArrayInputStream( bundleData ) ); setupBundle( start, startLevel, bundleContext, bundle ); return bundle.getBundleId(); } private static void setupBundle( boolean start, int startLevel, BundleContext bundleContext, Bundle bundle ) throws BundleException { StartLevel sl = ServiceLookup.getService( bundleContext, StartLevel.class ); sl.setBundleStartLevel( bundle, startLevel ); if( start ) { bundle.start(); } } public long installBundle( String bundleLocation, byte[] bundleData ) throws RemoteException, BundleException { Bundle bundle = framework.getBundleContext().installBundle( bundleLocation, new ByteArrayInputStream( bundleData ) ); return bundle.getBundleId(); } public void startBundle( long bundleId ) throws RemoteException, BundleException { framework.getBundleContext().getBundle( bundleId ).start(); } public void stopBundle( long bundleId ) throws RemoteException, BundleException { framework.getBundleContext().getBundle( bundleId ).stop(); } public void setBundleStartLevel( long bundleId, int startLevel ) throws RemoteException, BundleException { BundleContext bc = framework.getBundleContext(); StartLevel sl = ServiceLookup.getService( bc, StartLevel.class ); Bundle bundle = bc.getBundle( bundleId ); sl.setBundleStartLevel( bundle, startLevel ); } public void uninstallBundle( long id ) throws RemoteException, BundleException { framework.getBundleContext().getBundle( id ).uninstall(); } public FrameworkFactory findFrameworkFactory() { ServiceLoader<FrameworkFactory> loader = ServiceLoader.load( FrameworkFactory.class ); FrameworkFactory factory = loader.iterator().next(); return factory; } private static Map<String, String> buildFrameworkProperties( String[] args ) { Map<String, String> props = new HashMap<String, String>(); for( String arg : args ) { if( arg.startsWith( "-F" ) ) { int eq = arg.indexOf( "=" ); if( eq == -1 ) { String key = arg.substring( 2 ); props.put( key, null ); } else { String key = arg.substring( 2, eq ); String value = arg.substring( eq + 1 ); props.put( key, value ); } } else { LOG.warning( "ignoring unknown argument " + arg ); } } return props; } public void callService( String filter, String methodName ) throws RemoteException, BundleException { try { LOG.fine( "acquiring service " + filter ); BundleContext bc = framework.getBundleContext(); Object service = ServiceLookup.getServiceByFilter( bc, filter ); Class<? extends Object> klass = service.getClass(); Method method; try { method = klass.getMethod( methodName, Object[].class ); LOG.fine( "calling service method " + method ); method.invoke( service, (Object) new Object[]{} ); } catch ( NoSuchMethodException e ) { method = klass.getMethod( methodName ); LOG.fine( "calling service method " + method ); method.invoke( service ); } } catch ( SecurityException exc ) { throw new IllegalStateException( exc ); } catch ( NoSuchMethodException exc ) { throw new IllegalStateException( exc ); } catch ( IllegalArgumentException exc ) { throw new IllegalStateException( exc ); } catch ( IllegalAccessException exc ) { throw new IllegalStateException( exc ); } catch ( InvocationTargetException exc ) { throw new IllegalStateException( exc ); } } public void setFrameworkStartLevel( int startLevel ) throws RemoteException { setFrameworkStartLevel( startLevel, 0 ); } public boolean setFrameworkStartLevel( final int startLevel, long timeout ) throws RemoteException { BundleContext bc = framework.getBundleContext(); final StartLevel sl = ServiceLookup.getService( bc, StartLevel.class ); final CountDownLatch latch = new CountDownLatch( 1 ); bc.addFrameworkListener( new FrameworkListener() { public void frameworkEvent( FrameworkEvent frameworkEvent ) { switch( frameworkEvent.getType() ) { case FrameworkEvent.STARTLEVEL_CHANGED: if( sl.getStartLevel() == startLevel ) { latch.countDown(); } } } } ); sl.setStartLevel( startLevel ); boolean startLevelReached; try { startLevelReached = latch.await( timeout, TimeUnit.MILLISECONDS ); return startLevelReached; } catch ( InterruptedException exc ) { throw new RemoteException( "interrupted while waiting", exc ); } } public void waitForState( long bundleId, int state, long timeoutInMillis ) throws RemoteException, BundleException { throw new UnsupportedOperationException( "not yet implemented" ); } public int getBundleState( long bundleId ) throws RemoteException, BundleException { Bundle bundle = framework.getBundleContext().getBundle( bundleId ); if( bundle == null ) { throw new BundleException( String.format( "bundle [%d] does not exist", bundleId ) ); } return bundle.getState(); } public RemoteServiceReference[] getServiceReferences( String filter ) throws RemoteException, BundleException, InvalidSyntaxException { return getServiceReferences( filter, -1, null ); } public RemoteServiceReference[] getServiceReferences( String filter, long timeout, TimeUnit timeUnit ) throws RemoteException, BundleException, InvalidSyntaxException { BundleContext bundleContext = framework.getBundleContext(); ServiceReference[] serviceReferences = bundleContext.getAllServiceReferences( null, filter ); if( serviceReferences == null ) { if( timeout < 0 ) { return new RemoteServiceReference[0]; } ServiceTracker tracker = new ServiceTracker( bundleContext, bundleContext.createFilter( filter ), null ); tracker.open( true ); try { tracker.waitForService( timeUnit.toMillis( timeout ) ); serviceReferences = tracker.getServiceReferences(); if( serviceReferences == null ) { throw new IllegalStateException( "services vanished too fast..." ); } } catch ( InterruptedException e ) { throw new RuntimeException( "interrupted!", e ); } finally { tracker.close(); } } RemoteServiceReference[] remoteRefs = new RemoteServiceReference[serviceReferences.length]; for( int i = 0; i < remoteRefs.length; i++ ) { ServiceReference reference = serviceReferences[i]; final String serviceFilter = "(&(" + Constants.SERVICE_ID + "=" + reference.getProperty( Constants.SERVICE_ID ) + ")" + filter + ")"; final String[] keys = reference.getPropertyKeys(); final Map<String, Object> values = new HashMap<String, Object>(); for( String key : keys ) { values.put( key, reference.getProperty( key ) ); } remoteRefs[i] = new RemoteServiceReferenceImpl( values, serviceFilter ); } return remoteRefs; } public Object invokeMethodOnService( RemoteServiceReference reference, String methodName, Object... args ) throws RemoteException, Exception { Class<?>[] argTypes = new Class<?>[args.length]; for( int i = 0; i < argTypes.length; i++ ) { Object object = args[i]; if( object == null ) { throw new IllegalArgumentException( "argument " + i + " is null, use invokeMethodOnService(RemoteServiceReference, String, Class[], Object[]) if you want to call a service with null argument values" ); } argTypes[i] = object.getClass(); } return invokeMethodOnService( reference, methodName, argTypes, args ); } public Object invokeMethodOnService( RemoteServiceReference reference, String methodName, Class<?>[] parameterTypes, Object[] args ) throws RemoteException, Exception { BundleContext bundleContext = framework.getBundleContext(); ServiceReference[] allServiceReferences = bundleContext.getAllServiceReferences( null, reference.getServiceFilter() ); if( allServiceReferences == null || allServiceReferences.length == 0 ) { throw new IllegalStateException( "service is no longer present" ); } if( allServiceReferences.length > 1 ) { throw new AssertionError( "more than one service is matching the reference, this should never happen" ); } Object service = bundleContext.getService( allServiceReferences[0] ); if( service == null ) { throw new IllegalStateException( "service has vanished between calls" ); } try { Method method = service.getClass().getMethod( methodName, parameterTypes ); return method.invoke( service, args ); } finally { bundleContext.ungetService( allServiceReferences[0] ); } } public static void main( String[] args ) throws RemoteException, AlreadyBoundException, BundleException, InterruptedException { LOG.fine( "starting RemoteFrameworkImpl" ); Map<String, String> props = buildFrameworkProperties( args ); new RemoteFrameworkImpl( props ); } }