FELIX-914 Ensure remote shell connections are terminated
when the remote shell bundle is stopped.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@739941 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java b/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
index 689cdda..396699c 100644
--- a/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
+++ b/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
@@ -24,6 +24,8 @@
 import java.net.Socket;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.osgi.framework.BundleContext;
 
@@ -42,6 +44,7 @@
     private AtomicInteger m_UseCounter;
     private int m_MaxConnections;
     private int m_SoTimeout;
+    private Set m_connections;
 
 
     /**
@@ -55,6 +58,7 @@
         m_SoTimeout = getProperty( bundleContext, "osgi.shell.telnet.socketTimeout", 0 );
         m_MaxConnections = getProperty( bundleContext, "osgi.shell.telnet.maxconn", 2 );
         m_UseCounter = new AtomicInteger( 0 );
+        m_connections = new HashSet();
         m_ListenerThread = new Thread( new Acceptor(), "telnetconsole.Listener" );
         m_ListenerThread.start();
     }//activate
@@ -80,6 +84,23 @@
         {
             Activator.getServices().error( "Listener::deactivate()", ex );
         }
+        
+        // get the active connections (and clear the list)
+        // we have to work on a copy, since stopping any active connection
+        // will try to remove itself from the set, which might cause a
+        // ConcurrentModificationException if we would iterate over the list
+        Shell[] connections;
+        synchronized ( m_connections )
+        {
+            connections = ( Shell[] ) m_connections.toArray( new Shell[m_connections.size()] );
+            m_connections.clear();
+        }
+
+        // now terminate all active connections
+        for ( int i = 0; i < connections.length; i++ )
+        {
+            connections[i].terminate();
+        }
     }//deactivate
 
     /**
@@ -128,7 +149,8 @@
                         {
                             m_UseCounter.increment();
                             //run on the connection thread
-                            Thread connectionThread = new Thread( new Shell( s, m_UseCounter ) );
+                            Thread connectionThread = new Thread( new Shell( Listener.this, s, m_UseCounter ) );
+                            connectionThread.setName( "telnetconsole.shell remote=" + s.getRemoteSocketAddress() );
                             connectionThread.start();
                         }
                     }
@@ -177,10 +199,46 @@
         String propValue = bundleContext.getProperty( propName );
         if ( propValue != null )
         {
-           return propValue;
+            return propValue;
         }
 
         return defaultValue;
     }
 
+    /**
+     * Registers the given {@link Shell} instance handling a remote connection
+     * to this listener.
+     * <p>
+     * This method is called by the {@link Shell#run()} method to register the
+     * remote connection for it to be terminated in case this listener is
+     * {@link #deactivate() deactivated} before the remote connection is
+     * terminated.
+     * 
+     * @param connection The {@link Shell} connection to register
+     */
+    void registerConnection( Shell connection )
+    {
+        synchronized ( m_connections )
+        {
+            m_connections.add( connection );
+        }
+    }
+
+    /**
+     * Unregisters the given {@link Shell} instance handling a remote connection
+     * from this listener.
+     * <p>
+     * This method is called when the {@link Shell#run()} method terminates to
+     * inform this listener instance that the remote connection has ended and
+     * thus does not need to be cleaned up when this listener terminates.
+     * 
+     * @param connection The {@link Shell} connection to unregister
+     */
+    void unregisterConnection( Shell connection )
+    {
+        synchronized ( m_connections )
+        {
+            m_connections.remove( connection );
+        }
+    }
 }//class Listener
diff --git a/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java b/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
index 636a4e9..f14c026 100644
--- a/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
+++ b/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
@@ -27,26 +27,44 @@
 
 /**
  * Implements the shell.
+ * <p>
+ * This class is instantiated by the {@link Listener} thread to handle a single
+ * remote connection in its own thread. The connection handler thread either
+ * terminates on request by the remote end or by the Remote Shell bundle being
+ * stopped. In the latter case, the {@link #terminate()} method is called, which
+ * closes the Socket used to handle the remote console. This causes a
+ * <code>SocketException</code> in the handler thread reading from the socket
+ * which in turn causes the {@link #run()} method to terminate and thus to
+ * end the handler thread. 
  */
 class Shell implements Runnable
 {
 
+    private Listener m_owner;
     private Socket m_Socket;
     private AtomicInteger m_UseCounter;
 
 
-    public Shell( Socket s, AtomicInteger counter )
+    public Shell( Listener owner, Socket s, AtomicInteger counter )
     {
+        m_owner = owner;
         m_Socket = s;
         m_UseCounter = counter;
     }//constructor
 
-
+    void terminate()
+    {
+        // called by Listener.deactivate() to terminate this session
+        exit( "\r\nFelix Remote Shell Console Terminating" );
+    }//terminate
+    
     /**
      * Runs the shell.
      */
     public void run()
     {
+        m_owner.registerConnection( this );
+        
         try
         {
             PrintStream out = new TerminalPrintStream( m_Socket.getOutputStream() );
@@ -55,8 +73,8 @@
 
             // Print welcome banner.
             out.println();
-            out.println( "Felix Shell Console:" );
-            out.println( "=====================" );
+            out.println( "Felix Remote Shell Console:" );
+            out.println( "============================" );
             out.println( "" );
 
             do
@@ -69,22 +87,18 @@
                     //make sure to capture end of stream
                     if ( line == null )
                     {
-                        exit();
+                        out.println( "exit" );
                         return;
                     }
                 }
                 catch ( Exception ex )
                 {
-                    exit();
                     return;
                 }
 
                 line = line.trim();
                 if ( line.equalsIgnoreCase( "exit" ) || line.equalsIgnoreCase( "disconnect" ) )
                 {
-                    in.close();
-                    out.close();
-                    exit();
                     return;
                 }
 
@@ -109,11 +123,32 @@
         {
             Activator.getServices().error( "Shell::run()", ex );
         }
+        finally
+        {
+            // no need to clean up in/out, since exit does it all
+            exit( null );
+        }
     }//run
 
 
-    private void exit()
+    private void exit(String message)
     {
+        // farewell message
+        try
+        {
+            PrintStream out = new TerminalPrintStream( m_Socket.getOutputStream() );
+            if ( message != null )
+            {
+                out.println( message );
+            }
+            out.println( "Good Bye!" );
+            out.close();
+        }
+        catch ( IOException ioe )
+        {
+            // ignore
+        }
+
         try
         {
             m_Socket.close();
@@ -122,6 +157,7 @@
         {
             Activator.getServices().error( "Shell::exit()", ex );
         }
+        m_owner.unregisterConnection( this );
         m_UseCounter.decrement();
     }//exit