add trivial socket based control layer for framework launcher


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@805542 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/common/runtime/.classpath b/sigil/common/runtime/.classpath
index ef51f6b..c9ffd60 100644
--- a/sigil/common/runtime/.classpath
+++ b/sigil/common/runtime/.classpath
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.launching.macosx.MacOSXType/Java 1.6.0"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry kind="con" path="org.apache.felix.sigil.classpathContainer"/>
 	<classpathentry kind="output" path="build/classes"/>
 </classpath>
diff --git a/sigil/common/runtime/build.xml b/sigil/common/runtime/build.xml
new file mode 100644
index 0000000..aaf48be
--- /dev/null
+++ b/sigil/common/runtime/build.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<project name="common.runtime" default="build">
+  <import file="../build.xml"/>
+</project>
diff --git a/sigil/common/runtime/ivy.xml b/sigil/common/runtime/ivy.xml
new file mode 100644
index 0000000..0a697df
--- /dev/null
+++ b/sigil/common/runtime/ivy.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+-->
+<ivy-module version="1.0">
+  <info 
+        organisation="org.apache"
+        module="felix.sigil.common.runtime"
+        status="integration"/>
+   <publications>
+     <artifact name="org.apache.felix.sigil.common.runtime" />
+   </publications>
+</ivy-module>
diff --git a/sigil/common/runtime/sigil.properties b/sigil/common/runtime/sigil.properties
index 2488647..79def6a 100644
--- a/sigil/common/runtime/sigil.properties
+++ b/sigil/common/runtime/sigil.properties
@@ -4,12 +4,20 @@
 -bundles: \
 	org.apache.felix.sigil.common.runtime, \
 
--sourcedirs: \
-	src, \
+-contents: \
+  org.apache.felix.sigil.common.runtime.*, \
+  org.apache.commons.cli.*, \
+  org.apache.commons.io.*, \
+
+-exports: \
+	org.apache.felix.sigil.common.runtime, \
 
 -imports: \
-	org.apache.commons.cli;version=1.2.0, \
-	org.osgi.framework, \
-	org.osgi.framework.launch;version=1.0.0, \
+	org.apache.commons.cli;version=1.2.0;resolve=compile, \
+	org.apache.commons.io.input;version=1.4.0;resolve=compile, \
+	org.osgi.framework;resolve=compile, \
+	org.osgi.framework.launch;version=1.0.0;resolve=compile, \
+
+header;Main-Class: org.apache.felix.sigil.common.runtime.Main
 
 # end
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java
new file mode 100644
index 0000000..61dabd2
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Client.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.felix.sigil.common.runtime.io.InstallAction;
+import org.apache.felix.sigil.common.runtime.io.StartAction;
+import org.apache.felix.sigil.common.runtime.io.StatusAction;
+import org.apache.felix.sigil.common.runtime.io.StopAction;
+import org.apache.felix.sigil.common.runtime.io.UninstallAction;
+import org.apache.felix.sigil.common.runtime.io.UpdateAction;
+import org.apache.felix.sigil.common.runtime.io.UpdateAction.Update;
+import org.osgi.framework.BundleException;
+
+
+/**
+ * @author dave
+ *
+ */
+public class Client
+{
+    public static final String PORT_PROPERTY = "port";
+    public static final String ADDRESS_PROPERTY = "address";
+    
+    private Socket socket;
+    private InputStream in;
+    private OutputStream out;
+
+
+    public Client()
+    {
+    }
+
+
+    public void connect(Properties props) throws IOException
+    {
+        InetAddress address = InetAddress.getByName( props.getProperty( ADDRESS_PROPERTY ) );
+        int port = Integer.parseInt( props.getProperty( PORT_PROPERTY, "0" ) );
+        socket = new Socket( address, port );
+        in = socket.getInputStream();
+        out = socket.getOutputStream();
+    }
+
+
+    public void close() throws IOException
+    {
+        socket.close();
+    }
+
+
+    public long install( String url ) throws IOException, BundleException
+    {
+        return new InstallAction( in, out ).client( url );
+    }
+
+
+    public void start( long bundle ) throws IOException, BundleException
+    {
+        new StartAction( in, out ).client();
+    }
+
+
+    public void stop( long bundle ) throws IOException, BundleException
+    {
+        new StopAction( in, out ).client();
+    }
+
+
+    public void uninstall( long bundle ) throws IOException, BundleException
+    {
+        new UninstallAction( in, out ).client();
+    }
+
+
+    public void update( long bundle ) throws IOException, BundleException
+    {
+        Update update = new UpdateAction.Update(bundle, null);
+        new UpdateAction( in, out ).client(update);
+    }
+
+
+    public void update( long bundle, String url ) throws IOException, BundleException
+    {
+        Update update = new UpdateAction.Update(bundle, url);
+        new UpdateAction( in, out ).client(update);
+    }
+
+
+    public Map<Long, String> status() throws IOException, BundleException
+    {
+        return new StatusAction( in, out ).client();
+    }
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Main.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Main.java
index c7ddab3..7409371 100644
--- a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Main.java
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Main.java
@@ -1,10 +1,14 @@
 package org.apache.felix.sigil.common.runtime;
 
-import java.io.File;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.ServiceLoader;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.HelpFormatter;
@@ -16,69 +20,133 @@
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 
+
 public class Main
 {
     private static final String COMMAND_LINE_SYNTAX = "java -jar org.apache.felix.sigil.common.runtime";
-    
+
     private static Framework framework;
     private static final Options options;
-    
-    static {
+
+    static
+    {
         options = new Options();
         options.addOption( "?", "help", false, "Print help for the Sigil launcher" );
+        options.addOption( "p", "port", true, "Port to launch server on (0 implies auto allocate) [default 0]" );
+        options.addOption( "a", "address", true, "Address to bind server to [default all]");
     }
 
-    public static void main( String[] args )
+
+    public static void main( String[] args ) throws Exception
     {
-        try {
+        FrameworkFactory factory = getFrameworkFactory();
+
+        try
+        {
             Parser parser = new PosixParser();
             CommandLine cl = parser.parse( options, args );
-            
-            if ( cl.hasOption( '?' ) ) {
+
+            if ( cl.hasOption( '?' ) )
+            {
                 printHelp();
             }
-            else {
-                ServiceLoader<FrameworkFactory> loader = ServiceLoader.load( FrameworkFactory.class );
-                FrameworkFactory factory = loader.iterator().next();
-                
+            else
+            {
                 Map<String, String> config = buildConfig( cl );
-                
+
                 framework = factory.newFramework( config );
                 framework.init();
-                
-                launch( args );
-                
+
+                Server server = launch( cl );
+
                 framework.waitForStop( 0 );
+
+                if ( server != null )
+                {
+                    server.stop();
+                }
             }
         }
-        catch (NoSuchElementException e) {
+        catch ( NoSuchElementException e )
+        {
             System.err.println( "No " + FrameworkFactory.class.getName() + " found on classpath" );
             System.exit( 1 );
         }
+        catch ( InterruptedException e )
+        {
+            System.err.println( "Interrupted prior to framework stop" );
+            System.exit( 1 );
+        }
+        catch ( ParseException e )
+        {
+            printHelp();
+            System.exit( 1 );
+        }
         catch ( BundleException e )
         {
             e.printStackTrace();
+            System.exit( 1 );
         }
-        catch ( InterruptedException e )
+        catch ( IOException e )
         {
-            System.err.println( "Interrupted prior to framework stop" );
-        }
-        catch ( ParseException e )
-        {
-            printHelp();
+            e.printStackTrace();
+            System.exit( 1 );
         }
     }
 
+    /**
+     * Simple method to parse META-INF/services file for framework factory.
+     * Currently, it assumes the first non-commented line is the class name
+     * of the framework factory implementation.
+     * @return The created <tt>FrameworkFactory</tt> instance.
+     * @throws Exception if any errors occur.
+    **/
+    private static FrameworkFactory getFrameworkFactory() throws Exception
+    {
+        URL url = Main.class.getClassLoader().getResource(
+            "META-INF/services/org.osgi.framework.launch.FrameworkFactory");
+        if (url != null)
+        {
+            BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
+            try
+            {
+                for (String s = br.readLine(); s != null; s = br.readLine())
+                {
+                    s = s.trim();
+                    // Try to load first non-empty, non-commented line.
+                    if ((s.length() > 0) && (s.charAt(0) != '#'))
+                    {
+                        return (FrameworkFactory) Class.forName(s).newInstance();
+                    }
+                }
+            }
+            finally
+            {
+                if (br != null) br.close();
+            }
+        }
+
+        throw new Exception("Could not find framework factory.");
+    }
+    
     private static Map<String, String> buildConfig( CommandLine cl )
     {
         HashMap<String, String> config = new HashMap<String, String>();
         return config;
     }
 
-    private static void launch( String[] args ) throws ParseException
+
+    private static Server launch( CommandLine line ) throws IOException
     {
+        Server server = new Server( framework );
+        String v = line.getOptionValue('a');
+        InetAddress addr = v == null ? null : InetAddress.getByName( v );
+        int port = Integer.parseInt( line.getOptionValue( 'p', "0" ) );
+        server.start( addr, port );
+        return server;
     }
-    
+
+
     private static void printHelp()
     {
         HelpFormatter f = new HelpFormatter();
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java
new file mode 100644
index 0000000..04724ef
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/Server.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.sigil.common.runtime.io.Action;
+import org.apache.felix.sigil.common.runtime.io.InstallAction;
+import org.apache.felix.sigil.common.runtime.io.StartAction;
+import org.apache.felix.sigil.common.runtime.io.StatusAction;
+import org.apache.felix.sigil.common.runtime.io.StopAction;
+import org.apache.felix.sigil.common.runtime.io.UninstallAction;
+import org.apache.felix.sigil.common.runtime.io.UpdateAction;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.*;
+
+
+/**
+ * @author dave
+ *
+ */
+public class Server
+{
+    private final Framework fw;
+    private Thread accept;
+    private ExecutorService read = Executors.newCachedThreadPool();
+
+    private AtomicBoolean stopped = new AtomicBoolean();
+
+
+    public Server( Framework fw )
+    {
+        this.fw = fw;
+    }
+
+
+    public void start( InetAddress inetAddress, int port ) throws IOException
+    {
+        final ServerSocket socket = new ServerSocket();
+        InetSocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
+        
+        socket.bind( socketAddress );
+
+        System.out.println( "Started server listening on " + socket.getLocalSocketAddress() + ":" + socket.getLocalPort() );
+        
+        accept = new Thread( new Runnable()
+        {
+            public void run()
+            {
+                while ( !stopped.get() )
+                {
+                    try
+                    {
+                        read.execute( new Reader( socket.accept() ) );
+                    }
+                    catch ( IOException e )
+                    {
+                        // TODO Auto-generated catch block
+                        e.printStackTrace();
+                    }
+                }
+            }
+        } );
+    }
+
+
+    public void stop()
+    {
+        stopped.set( true );
+        accept.interrupt();
+        accept = null;
+    }
+
+    public class Reader implements Runnable
+    {
+
+        private final Socket socket;
+
+
+        /**
+         * @param accept
+         */
+        public Reader( Socket socket )
+        {
+            this.socket = socket;
+        }
+
+
+        /* (non-Javadoc)
+         * @see java.lang.Runnable#run()
+         */
+        public void run()
+        {
+            try
+            {
+                InputStream in = socket.getInputStream();
+                OutputStream out = socket.getOutputStream();
+                while ( !stopped.get() )
+                {
+                    int action = in.read();
+                    Action<?, ?> task = null;
+                    switch ( action )
+                    {
+                        case INSTALL:
+                            task = new InstallAction( in, out );
+                            break;
+                        case START:
+                            task = new StartAction( in, out );
+                            break;
+                        case STOP:
+                            task = new StopAction( in, out );
+                            break;
+                        case UNINSTALL:
+                            task = new UninstallAction( in, out );
+                            break;
+                        case UPDATE:
+                            task = new UpdateAction( in, out );
+                            break;
+                        case STATUS:
+                            task = new StatusAction( in, out );
+                            break;
+                    }
+                    task.server( fw );
+                }
+            }
+            catch ( IOException e )
+            {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Action.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Action.java
new file mode 100644
index 0000000..0e6ceb5
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Action.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import org.apache.commons.io.input.CountingInputStream;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.OK;
+import static org.apache.felix.sigil.common.runtime.io.Constants.ERROR;
+
+
+/**
+ * @author dave
+ *
+ */
+public abstract class Action<I, O>
+{
+    private final ObjectInputStream in;
+    private final ObjectOutputStream out;
+
+
+    public Action( InputStream in, OutputStream out ) throws IOException
+    {
+        this.in = new ObjectInputStream( in );
+        this.out = new ObjectOutputStream( out );
+    }
+
+
+    public O client() throws IOException, BundleException
+    {
+        return client( null );
+    }
+
+
+    public abstract O client( I input ) throws IOException, BundleException;
+
+
+    public abstract void server( Framework fw ) throws IOException;
+
+
+    protected boolean checkOk() throws IOException
+    {
+        int ch = readInt();
+        switch ( ch )
+        {
+            case OK:
+                return true;
+            case ERROR:
+                return false;
+            default:
+                throw new IOException( "Unexpected return code " + ch );
+        }
+    }
+
+
+    protected void writeOk() throws IOException
+    {
+        writeInt( OK );
+    }
+
+
+    protected void writeError() throws IOException
+    {
+        writeInt( ERROR );
+    }
+
+
+    protected String readString() throws IOException
+    {
+        return in.readUTF();
+    }
+
+
+    protected void writeString( String str ) throws IOException
+    {
+        out.writeUTF( str );
+    }
+
+
+    protected void writeInt( int i ) throws IOException
+    {
+        out.writeInt( i );
+    }
+
+
+    protected int readInt() throws IOException
+    {
+        return in.readInt();
+    }
+
+
+    protected void writeLong( long l ) throws IOException
+    {
+        out.writeLong( l );
+    }
+
+
+    protected long readLong() throws IOException
+    {
+        return in.readLong();
+    }
+
+    protected void writeBoolean( boolean b ) throws IOException
+    {
+        out.writeBoolean( b );
+    }
+    
+    protected boolean readBoolean() throws IOException {
+        return in.readBoolean();
+    }
+    
+    protected void writeStream( String location ) throws IOException {
+        URL url = new URL( location );
+        URLConnection conn = url.openConnection();
+        conn.connect();
+        
+        int l = conn.getContentLength();
+        writeInt( l );
+        InputStream uin = conn.getInputStream();
+        byte[] buf = new byte[1024*1024];
+        for (;;) {
+            int r = uin.read( buf );
+            if ( r == -1 ) break;
+            out.write( buf, 0, r );
+        }
+    }
+    
+    protected InputStream readStream() throws IOException {
+        final int length = readInt();
+        return new CountingInputStream(in) {
+            @Override
+            public int read() throws IOException
+            {
+                if ( getCount() < length )
+                    return super.read();
+                else
+                    return -1;
+            }
+
+            @Override
+            public int read( byte[] b, int off, int len ) throws IOException
+            {
+                len = (getCount() + len) > length ? (length - getCount()) : len;
+                if ( len == 0 )
+                    return -1;
+                else 
+                    return super.read( b, off, len );
+            }
+
+            @Override
+            public int read( byte[] b ) throws IOException
+            {
+                return read( b, 0, b.length );
+            }
+
+            @Override
+            public long skip( long len ) throws IOException
+            {
+                len = (getCount() + len) > length ? (length - getCount()) : len;
+                if ( len == 0 )
+                    return -1;
+                else 
+                    return super.skip( len );
+            }  
+        };
+    }
+    
+    protected void flush() throws IOException
+    {
+        out.flush();
+    }
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java
new file mode 100644
index 0000000..58a614d
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/Constants.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+public interface Constants
+{
+    public static final int OK = 0;
+    public static final int ERROR = -1;
+
+    public static final int INSTALL = 1;
+    public static final int START = 2;
+    public static final int STOP = 4;
+    public static final int UNINSTALL = 8;
+    public static final int UPDATE = 16;
+    public static final int STATUS = 32;
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/InstallAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/InstallAction.java
new file mode 100644
index 0000000..fd43a20
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/InstallAction.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.INSTALL;
+
+
+/**
+ * @author dave
+ *
+ */
+public class InstallAction extends Action<String, Long>
+{
+
+    public InstallAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+    }
+
+
+    @Override
+    public Long client( String url ) throws IOException, BundleException
+    {
+        writeInt( INSTALL );
+        writeString( url );
+        if ( checkOk() )
+        {
+            return readLong();
+        }
+        else
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        String url = readString();
+        try
+        {
+            Bundle val = fw.getBundleContext().installBundle( url );
+            writeOk();
+            writeLong( val.getBundleId() );
+        }
+        catch ( BundleException e )
+        {
+            writeError();
+            writeString( e.getMessage() );
+        }
+
+    }
+
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StartAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StartAction.java
new file mode 100644
index 0000000..3590223
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StartAction.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.START;
+
+
+/**
+ * @author dave
+ *
+ */
+public class StartAction extends Action<Long, Void>
+{
+
+    public StartAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+    }
+
+
+    @Override
+    public Void client( Long bundle ) throws IOException, BundleException
+    {
+        writeInt( START );
+        writeLong( bundle );
+        if ( !checkOk() )
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+        return null;
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        long id = readLong();
+        Bundle b = fw.getBundleContext().getBundle( id );
+        if ( b == null )
+        {
+            writeError();
+            writeString( "Unknown bundle " + id );
+        }
+        else
+        {
+            try
+            {
+                b.start();
+                writeOk();
+            }
+            catch ( BundleException e )
+            {
+                writeError();
+                writeString( e.getMessage() );
+            }
+        }
+    }
+
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java
new file mode 100644
index 0000000..f5379d1
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StatusAction.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.STATUS;
+import static org.osgi.framework.Constants.BUNDLE_VERSION;
+
+
+/**
+ * @author dave
+ *
+ */
+public class StatusAction extends Action<Void, Map<Long, String>>
+{
+
+    public StatusAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+    }
+
+
+    @Override
+    public Map<Long, String> client( Void in ) throws IOException
+    {
+        writeInt(STATUS);
+        int num = readInt();
+        HashMap<Long, String> map = new HashMap<Long, String>(num);
+        
+        for (int i = 0; i < num; i++) {
+            long id = readLong();
+            String symbol = readString();
+            map.put( id, symbol );
+        }
+        
+        return map;
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        Bundle[] bundles = fw.getBundleContext().getBundles();
+        writeInt( bundles.length );
+        for ( Bundle b : bundles ) {
+            writeLong(b.getBundleId());
+            String symbol = b.getSymbolicName() + ":" + b.getHeaders().get( BUNDLE_VERSION );
+            writeString(symbol);
+        }
+    }
+
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StopAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StopAction.java
new file mode 100644
index 0000000..4735eb7
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/StopAction.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.STOP;
+
+
+/**
+ * @author dave
+ *
+ */
+public class StopAction extends Action<Long, Void>
+{
+
+    public StopAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+    }
+
+
+    @Override
+    public Void client( Long bundle ) throws IOException, BundleException
+    {
+        writeInt( STOP );
+        writeLong( bundle );
+        if ( !checkOk() )
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+        return null;
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        long id = readLong();
+        Bundle b = fw.getBundleContext().getBundle( id );
+        if ( b == null )
+        {
+            writeError();
+            writeString( "Unknown bundle " + id );
+        }
+        else
+        {
+            try
+            {
+                b.stop();
+                writeOk();
+            }
+            catch ( BundleException e )
+            {
+                writeError();
+                writeString( e.getMessage() );
+            }
+        }
+    }
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UninstallAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UninstallAction.java
new file mode 100644
index 0000000..c60d1c5
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UninstallAction.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.UNINSTALL;
+
+
+/**
+ * @author dave
+ *
+ */
+public class UninstallAction extends Action<Long, Void>
+{
+
+    public UninstallAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+        // TODO Auto-generated constructor stub
+    }
+
+
+    @Override
+    public Void client( Long bundle ) throws IOException, BundleException
+    {
+        writeInt( UNINSTALL );
+        writeLong( bundle );
+        if ( !checkOk() )
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+        return null;
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        long id = readLong();
+        Bundle b = fw.getBundleContext().getBundle( id );
+        if ( b == null )
+        {
+            writeError();
+            writeString( "Unknown bundle " + id );
+        }
+        else
+        {
+            try
+            {
+                b.uninstall();
+                writeOk();
+            }
+            catch ( BundleException e )
+            {
+                writeError();
+                writeString( e.getMessage() );
+            }
+        }
+    }
+
+}
diff --git a/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UpdateAction.java b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UpdateAction.java
new file mode 100644
index 0000000..6d56468
--- /dev/null
+++ b/sigil/common/runtime/src/org/apache/felix/sigil/common/runtime/io/UpdateAction.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.sigil.common.runtime.io;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import static org.apache.felix.sigil.common.runtime.io.Constants.UPDATE;
+
+
+/**
+ * @author dave
+ *
+ */
+public class UpdateAction extends Action<UpdateAction.Update, Void>
+{
+    public static class Update
+    {
+        final long bundleID;
+        final String location;
+
+
+        public Update( long bundleID, String location )
+        {
+            this.bundleID = bundleID;
+            this.location = location;
+        }
+    }
+
+
+    public UpdateAction( InputStream in, OutputStream out ) throws IOException
+    {
+        super( in, out );
+    }
+
+
+    @Override
+    public Void client( Update update ) throws IOException, BundleException
+    {
+        writeInt( UPDATE );
+        writeLong( update.bundleID );
+        if ( update.location == null )
+        {
+            writeBoolean( false );
+        }
+        else
+        {
+            writeBoolean( true );
+            writeStream( update.location );
+        }
+
+        if ( !checkOk() )
+        {
+            String msg = readString();
+            throw new BundleException( msg );
+        }
+
+        return null;
+    }
+
+
+    @Override
+    public void server( Framework fw ) throws IOException
+    {
+        long id = readLong();
+        Bundle b = fw.getBundleContext().getBundle( id );
+        if ( b == null )
+        {
+            writeError();
+            writeString( "Unknown bundle " + id );
+        }
+        else
+        {
+            try
+            {
+                boolean remote = readBoolean();
+                if ( remote )
+                {
+                    InputStream in = readStream();
+                    b.update(in);
+                }
+                else
+                {
+                    b.update();
+                    writeOk();
+                }
+            }
+            catch ( BundleException e )
+            {
+                writeError();
+                writeString( e.getMessage() );
+            }
+        }
+
+    }
+}