Revision
16127
Author
dvarnes
Date
2010-01-03 08:54:19 -0600 (Sun, 03 Jan 2010)

Log Message

Initial commit - java ESL inbound/outbound client implementation

Added Paths

Diff

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/.project (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/.project	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/.project	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.freeswitch.esl</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/.project
___________________________________________________________________
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/.settings/org.maven.ide.eclipse.prefs (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/.settings/org.maven.ide.eclipse.prefs	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/.settings/org.maven.ide.eclipse.prefs	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,9 @@
+#Sat Dec 12 17:33:34 EST 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.classpath (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.classpath	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.classpath	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.classpath
___________________________________________________________________
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.project (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.project	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.project	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.freeswitch.esl.client.netty</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.project
___________________________________________________________________
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.eclipse.jdt.core.prefs (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.eclipse.jdt.core.prefs	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.eclipse.jdt.core.prefs	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,13 @@
+#Sun Jan 03 23:33:57 EST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.5

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.maven.ide.eclipse.prefs (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.maven.ide.eclipse.prefs	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/.settings/org.maven.ide.eclipse.prefs	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,9 @@
+#Sat Dec 12 17:44:54 EST 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/TODO.txt (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/TODO.txt	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/TODO.txt	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,21 @@
+
+As at 2010.01.03
+
+Features still to be implemented
+ * More testing of the outbound socket client
+ * Send event command
+ * Send message command for inbound client
+ * Refactor the api of the inbound client into the abstract handler so it is also available in outbound handlers
+ * Per event(s) listener
+ * Provide XML event handling - not sure if this is useful since the raw event is not exposed, although it could be if needed.
+ * 'myevent' event subscription - not a priority here, easy if required.
+ * Provide timeout protection on the client.sendSyncApiCommand().  It will currently block for ever.
+ * Working examples in an example project (started).
+ 
+Quality items
+ * Ask FS dev team for any ESL event generation test strategies/suites.
+ * Are all message header names in the enum ?
+ * Improve Javadoc coverage
+ * Add cross references to the FS wiki in the Javadocs .. eg list of api commands and events.
+ * Add unit test coverage
+ * Investigate availability of FS JIRA for issue tracking
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/TODO.txt
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/pom.xml (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/pom.xml	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/pom.xml	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2010 david varnes.
+ *
+ * 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.freeswitch.esl.client</groupId>
+    <artifactId>esl-client-parent</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+  </parent>
+  <artifactId>org.freeswitch.esl.client</artifactId>
+  <name>FreeSWITCH Event Socket Library - Java Client</name>
+  <packaging>bundle</packaging>
+  <build>
+  
+    <plugins>
+    
+      <!--  this plugin will create the target jar as a correct OSGi bundle  -->
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.0.1</version>
+        <extensions>true</extensions>
+        <configuration>
+          <unpackBundle>true</unpackBundle>
+          <instructions>
+            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+            <Bundle-Version>${project.version}</Bundle-Version>
+            <Bundle-DocURL>http://www.freeswitch.org</Bundle-DocURL>
+            <Import-Package>*</Import-Package>
+            <Export-Package>
+              org.freeswitch.esl.client,
+              org.freeswitch.esl.client.transport.event,
+              org.freeswitch.esl.client.transport.message,
+            </Export-Package>
+            <!--  the debug package is a temporary hack  -->
+            <Private-Package>
+              org.freeswitch.esl.client.internal,
+              org.freeswitch.esl.client.debug
+            </Private-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+      
+    </plugins>
+    
+  </build>
+  <dependencies>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.5.10</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.jboss.netty</groupId>
+      <artifactId>netty</artifactId>
+      <version>3.1.5.GA</version>
+    </dependency>
+    
+    <!--  test dependencies -->
+    
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.7</version>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <version>0.9.18</version>
+      <scope>test</scope>
+    </dependency>
+    
+  </dependencies>
+</project>
\ No newline at end of file
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/pom.xml
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/IEslEventListener.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/IEslEventListener.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/IEslEventListener.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client;
+
+import org.freeswitch.esl.client.transport.event.EslEvent;
+
+/**
+ * Interface for observers wanting to be notified of incoming FreeSWITCH Event Socket events.
+ * </p>
+ * Incoming events arrive asynchronously and are processed into two queues, one for server 
+ * initiated events, and one for the results of client requested background jobs.
+ * </p>  
+ * Each queue is serviced by a different thread pool (to ensure lowest latency for event-driven events) 
+ * and each queue is guaranteed to be processed (and listeners notified) in the order in which the
+ * events are received off the wire.
+ * </p>
+ * This design ensures that incoming event processing is not blocked by any long-running listener process.
+ * However multiple listeners will be notified sequentially, and so one slow listener can cause latency
+ * to other listeners.
+ *   
+ * @author  david varnes
+ * @version $Id$
+ */
+public interface IEslEventListener
+{
+    /**
+     * Signal of a server initiated event.
+     * 
+     * @param event as an {@link EslEvent} 
+     */
+    void eventReceived( EslEvent event );
+    
+    /**
+     * Signal of an event containing the result of a client requested background job.  The Job-UUID will
+     * be available as an event header of that name.
+     * 
+     * @param event as an {@link EslEvent}
+     */
+    void backgroundJobResultReceived( EslEvent event );
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/IEslEventListener.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/Client.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/Client.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/Client.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.inbound;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.freeswitch.esl.client.IEslEventListener;
+import org.freeswitch.esl.client.internal.IEslProtocolListener;
+import org.freeswitch.esl.client.transport.CommandResponse;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Entry point to connect to a running FreeSWITCH Event Socket Library module, as a client.
+ * </p>
+ * This class provides what the FreeSWITCH documentation refers to as an 'Inbound' connection
+ * to the Event Socket module. That is, with reference to the socket listening on the FreeSWITCH
+ * server, this client occurs as an inbound connection to the server.
+ * </p>
+ * See http://wiki.freeswitch.org/wiki/Mod_event_socket
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class Client
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+    
+    private final List<IEslEventListener> eventListeners = new CopyOnWriteArrayList<IEslEventListener>();
+    private final Executor eventListenerExecutor = Executors.newSingleThreadExecutor( 
+        new ThreadFactory()
+        {
+            AtomicInteger threadNumber = new AtomicInteger( 1 );
+            public Thread newThread( Runnable r )
+            {
+                return new Thread( r, "EslEventNotifier-" + threadNumber.getAndIncrement() );
+            }
+        });
+    private final Executor backgroundJobListenerExecutor = Executors.newSingleThreadExecutor( 
+        new ThreadFactory()
+        {
+            AtomicInteger threadNumber = new AtomicInteger( 1 );
+            public Thread newThread( Runnable r )
+            {
+                return new Thread( r, "EslBackgroundJobNotifier-" + threadNumber.getAndIncrement() );
+            }
+        });
+    
+    private AtomicBoolean authenticatorResponded = new AtomicBoolean( false );
+    private boolean authenticated;
+    private CommandResponse authenticationResponse;
+    private Channel channel;
+    
+    public boolean canSend()
+    {
+        return channel != null && channel.isConnected() && authenticated; 
+    }
+    
+    public void addEventListener( IEslEventListener listener )
+    {
+        if ( listener != null )
+        {
+            eventListeners.add( listener );
+        }
+    }
+
+    /**
+     * Attempt to establish an authenticated connection to the nominated FreeSWITCH ESL server socket.
+     * This call will block, waiting for an authentication handshake to occur, or timeout after the
+     * supplied number of seconds.  
+     *  
+     * @param host can be either ip address or hostname
+     * @param port tcp port that server socket is listening on (set in event_socket_conf.xml)
+     * @param password server event socket is expecting (set in event_socket_conf.xml) 
+     * @param timeoutSeconds number of seconds to wait for the server socket before aborting
+     */
+    public void connect( String host, int port, String password, int timeoutSeconds ) throws InboundConnectionFailure
+    {
+        // If already connected, disconnect first
+        if ( canSend() )
+        {
+            close();
+        }
+        
+        // Configure this client
+        ClientBootstrap bootstrap = new ClientBootstrap(
+            new NioClientSocketChannelFactory( 
+                Executors.newCachedThreadPool(), 
+                Executors.newCachedThreadPool() ) ); 
+        
+        // Add ESL handler and factory
+        InboundClientHandler handler = new InboundClientHandler( password, protocolListener );
+        bootstrap.setPipelineFactory( new InboundPipelineFactory( handler ) );
+        
+        // Attempt connection
+        ChannelFuture future = bootstrap.connect( new InetSocketAddress( host, port ) );
+        
+        // Wait till attempt succeeds, fails or timeouts
+        if ( ! future.awaitUninterruptibly( timeoutSeconds, TimeUnit.SECONDS ) )
+        {
+            throw new InboundConnectionFailure( "Timeout connecting to " + host + ":" + port );
+        }
+        // Did not timeout 
+        channel = future.getChannel();
+        // But may have failed anyway
+        if ( !future.isSuccess() )
+        {
+            log.warn( "Failed to connect to [{}:{}]", host, port );
+            log.warn( "  * reason: {}", future.getCause() );
+            
+            channel = null;
+            bootstrap.releaseExternalResources();
+            
+            throw new InboundConnectionFailure( "Could not connect to " + host + ":" + port, future.getCause() );
+        }
+        
+        //  Wait for the authentication handshake to call back
+        while ( ! authenticatorResponded.get() )
+        {
+            try
+            {
+                Thread.sleep( 250 );
+            } 
+            catch ( InterruptedException e )
+            {
+                // ignore
+            }
+        }
+        
+        if ( ! authenticated )
+        {
+            throw new InboundConnectionFailure( "Authentication failed: " + authenticationResponse.getReplyText() );
+        }
+    }
+    
+    /**
+     * Sends a FreeSWITCH API command to the server and blocks, waiting for an immediate response from the 
+     * server.
+     * <p/>
+     * The outcome of the command from the server is retured in an {@link EslMessage} object.
+     * 
+     * @param command API command to send
+     * @param arg command arguments
+     * @return an {@link EslMessage} containing command results
+     */
+    public EslMessage sendSyncApiCommand( String command, String arg )
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( command != null && !command.isEmpty() )
+        {
+            sb.append( "api " );
+            sb.append( command );
+        }
+        if ( arg != null && !arg.isEmpty() )
+        {
+            sb.append( ' ' );
+            sb.append( arg );
+        }
+
+        return handler.sendSyncSingleLineCommand( channel, sb.toString() );
+    }
+    
+    /**
+     * Submit a FreeSWITCH API command to the server to be executed in background mode. A synchronous 
+     * response from the server provides a UUID to identify the job execution results. When the server
+     * has completed the job execution it fires a BACKGROUND_JOB Event with the execution results.<p/>
+     * Note that this Client must be subscribed in the normal way to BACKGOUND_JOB Events, in order to 
+     * receive this event.
+     *     
+     * @param command API command to send
+     * @param arg command arguments
+     * @return String Job-UUID that the server will tag result event with.
+     */
+    public String sendAsyncApiCommand( String command, String arg )
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( command != null && !command.isEmpty() )
+        {
+            sb.append( "bgapi " );
+            sb.append( command );
+        }
+        if ( arg != null && !arg.isEmpty() )
+        {
+            sb.append( ' ' );
+            sb.append( arg );
+        }
+        
+        return handler.sendAsyncCommand( channel, sb.toString() );
+    }
+    
+    /**
+     * Set the current event subscription for this connection to the server.  Examples of the events 
+     * argument are:
+     * <pre>
+     *   ALL
+     *   CHANNEL_CREATE CHANNEL_DESTROY HEARTBEAT
+     *   CUSTOM conference::maintenance
+     *   CHANNEL_CREATE CHANNEL_DESTROY CUSTOM conference::maintenance sofia::register sofia::expire
+     * </pre> 
+     * Subsequent calls to this method replaces any previous subscriptions that were set.
+     * </p>
+     * Note: current implementation can only process 'plain' events.
+     * 
+     * @param format can be { plain | xml }
+     * @param events { all | space separated list of events } 
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse setEventSubscriptions( String format, String events )
+    {
+        // temporary hack
+        if ( ! format.equals( "plain" ) )
+        {
+            throw new IllegalStateException( "Only 'plain' event format is supported at present" );
+        }
+        
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( format != null && !format.isEmpty() )
+        {
+            sb.append( "event " );
+            sb.append( format );
+        }
+        if ( events != null && !events.isEmpty() )
+        {
+            sb.append( ' ' );
+            sb.append( events );
+        }
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() );
+
+        return new CommandResponse( sb.toString(), response );
+    }
+    
+    /**
+     * Cancel any existing event subscription.
+     * 
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse cancelEventSubscriptions()
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, "noevents" );
+
+        return new CommandResponse( "noevents", response );
+    }
+
+    /**
+     * Add an event filter to the current set of event filters on this connection. Any of the event headers
+     * can be used as a filter.
+     * </p>
+     * Note that event filters follow 'filter-in' semantics. That is, when a filter is applied
+     * only the filtered values will be received. Multiple filters can be added to the current
+     * connection.
+     * </p>
+     * Example filters:
+     * <pre>
+     *    eventHeader        valueToFilter
+     *    ----------------------------------
+     *    Event-Name         CHANNEL_EXECUTE
+     *    Channel-State      CS_NEW
+     * </pre>
+     * 
+     * @param eventHeader to filter on
+     * @param valueToFilter the value to match
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse addEventFilter( String eventHeader, String valueToFilter )
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( eventHeader != null && !eventHeader.isEmpty() )
+        {
+            sb.append( "filter " );
+            sb.append( eventHeader );
+        }
+        if ( valueToFilter != null && !valueToFilter.isEmpty() )
+        {
+            sb.append( ' ' );
+            sb.append( valueToFilter );
+        }
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() );
+        
+        return new CommandResponse( sb.toString(), response );
+    }
+    
+    /**
+     * Delete an event filter from the current set of event filters on this connection.  See  
+     * {@link Client.addEventFilter}   
+     * 
+     * @param eventHeader   to remove
+     * @param valueToFilter to remove
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse deleteEventFilter( String eventHeader, String valueToFilter )
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( eventHeader != null && !eventHeader.isEmpty() )
+        {
+            sb.append( "filter delete " );
+            sb.append( eventHeader );
+        }
+        if ( valueToFilter != null && !valueToFilter.isEmpty() )
+        {
+            sb.append( ' ' );
+            sb.append( valueToFilter );
+        }
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() );
+
+        return new CommandResponse( sb.toString(), response );
+    }
+
+    /**
+     * Enable log output.
+     * 
+     * @param level using the same values as in console.conf
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse setLoggingLevel( String level )
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        StringBuilder sb = new StringBuilder();
+        if ( level != null && !level.isEmpty() )
+        {
+            sb.append( "log " );
+            sb.append( level );
+        }
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, sb.toString() );
+
+        return new CommandResponse( sb.toString(), response );
+    }
+
+    /**
+     * Disable any logging previously enabled with setLogLevel().
+     * 
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse cancelLogging()
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, "nolog" );
+
+        return new CommandResponse( "nolog", response );
+    }
+
+    /**
+     * Close the socket connection
+     * 
+     * @return a {@link CommandResponse} with the server's response.
+     */
+    public CommandResponse close()
+    {
+        checkConnected();
+        InboundClientHandler handler = (InboundClientHandler)channel.getPipeline().getLast();
+        EslMessage response = handler.sendSyncSingleLineCommand( channel, "exit" );
+
+        return new CommandResponse( "exit", response );
+    }
+    
+    /*
+     *  Internal observer of the ESL protocol 
+     */
+    private final IEslProtocolListener protocolListener = new IEslProtocolListener()
+    {
+        public void authResponseReceived( CommandResponse response )
+        {
+            authenticatorResponded.set( true );
+            authenticated = response.isOk();
+            authenticationResponse = response;
+            log.debug( "Auth response success={}, message=[{}]", authenticated, response.getReplyText() );
+        }
+        
+        public void eventReceived( final EslEvent event )
+        {
+            log.debug( "Event received [{}]", event );
+            /*
+             *  Notify listeners in a different thread in order to:
+             *    - not to block the IO threads with potentially long-running listeners
+             *    - generally be defensive running other people's code
+             *  Use a different worker thread pool for async job results than for event driven
+             *  events to keep the latency as low as possible.
+             */
+            if ( event.getEventName().equals( "BACKGROUND_JOB" ) )
+            {
+                for ( final IEslEventListener listener : eventListeners )
+                {
+                    backgroundJobListenerExecutor.execute( new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                listener.backgroundJobResultReceived( event );
+                            }
+                            catch ( Throwable t )
+                            {
+                                log.error( "Error caught notifying listener of job result [" + event + ']', t );
+                            }
+                        }
+                    } );
+                }
+            }
+            else
+            {
+                for ( final IEslEventListener listener : eventListeners )
+                {
+                    eventListenerExecutor.execute( new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                listener.eventReceived( event );
+                            }
+                            catch ( Throwable t )
+                            {
+                                log.error( "Error caught notifying listener of event [" + event + ']', t );
+                            }
+                        }
+                    } );
+                }
+            }
+        }
+
+        public void disconnected()
+        {
+            log.info( "Disconnected .." );
+        }
+    };
+    
+    private void checkConnected()
+    {
+        if ( ! canSend() )
+        {
+            throw new IllegalStateException( "Not connected to FreeSWITCH Event Socket" );
+        }
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/Client.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundClientHandler.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundClientHandler.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundClientHandler.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.inbound;
+
+import org.freeswitch.esl.client.internal.AbstractEslClientHandler;
+import org.freeswitch.esl.client.internal.IEslProtocolListener;
+import org.freeswitch.esl.client.transport.CommandResponse;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Value;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipelineCoverage;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+
+/**
+ * End users of the inbound {@link Client} should not need to use this class. 
+ * </p>
+ * Specialised {@link AbstractEslClientHandler} that implements the connection logic for an 
+ * 'Inbound' FreeSWITCH Event Socket connection.  The responsibilities for this class are:
+ * </p>
+ * * To handle the auth request that the FreeSWITCH server will send immediately following a new 
+ * connection when mode is Inbound.
+ * </p>
+ * * To signal the observing {@link IEslProtocolListener} (expected to be the Inbound client 
+ * implementation when ESL events are received.
+ * </p>
+ * Note: implementation requirement is that an {@link ExecutionHandler} is placed in the processing 
+ * pipeline prior to this handler. This will ensure that each incoming message is processed in its
+ * own thread (although still guaranteed to be processed in the order of receipt).
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+@ChannelPipelineCoverage( "one" )
+public class InboundClientHandler extends AbstractEslClientHandler
+{
+    private final String password;
+    private final IEslProtocolListener listener;
+    
+    public InboundClientHandler( String password, IEslProtocolListener listener )
+    {
+        this.password = password;
+        this.listener = listener;
+    }
+    
+    protected void handleEslEvent( ChannelHandlerContext ctx, EslEvent event )
+    {
+        log.debug( "Received event: [{}]", event );
+        listener.eventReceived( event );
+    }
+
+    protected void handleAuthRequest( ChannelHandlerContext ctx )
+    {
+        log.debug( "Auth requested, sending [auth {}]", "*****" );
+        EslMessage response = sendSyncSingleLineCommand( ctx.getChannel(), "auth " + password );
+        log.debug( "Auth response [{}]", response );
+        if ( response.getContentType().equals( Value.COMMAND_REPLY ) )
+        {
+            CommandResponse commandResponse = new CommandResponse( "auth " + password, response );
+            listener.authResponseReceived( commandResponse );
+        }
+        else
+        {
+            log.error( "Bad auth response message [{}]", response );
+            throw new IllegalStateException( "Incorrect auth response" ); 
+        }
+    }
+
+    @Override
+    protected void handleDisconnectionNotice()
+    {
+        log.debug( "Received disconnection notice" );
+        listener.disconnected();
+    }
+    
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundClientHandler.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundConnectionFailure.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundConnectionFailure.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundConnectionFailure.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.inbound;
+
+/**
+ * Checked exception to handle connection failures.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class InboundConnectionFailure extends Exception
+{
+    private static final long serialVersionUID = 1L;
+
+    public InboundConnectionFailure( String message )
+    {
+        super( message );
+    }
+    
+    public InboundConnectionFailure( String message, Throwable cause )
+    {
+        super( message, cause );
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundConnectionFailure.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundPipelineFactory.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundPipelineFactory.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundPipelineFactory.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.inbound;
+
+import org.freeswitch.esl.client.internal.debug.ExecutionHandler;
+import org.freeswitch.esl.client.transport.message.EslMessageDecoder;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandler;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
+import org.jboss.netty.handler.codec.string.StringDecoder;
+import org.jboss.netty.handler.codec.string.StringEncoder;
+import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
+
+/**
+ * End users of the {@link Client} should not need to use this class. 
+ * </p>
+ * Convenience factory to assemble a Netty processing pipeline for inbound clients.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class InboundPipelineFactory implements ChannelPipelineFactory
+{
+    private final ChannelHandler handler;
+    
+    private final ChannelBuffer[] delimiters = 
+        new ChannelBuffer[] { ChannelBuffers.wrappedBuffer( new byte[] { '\n' } ),
+                              ChannelBuffers.wrappedBuffer( new byte[] { '\n', '\n' } ) };
+    
+    public InboundPipelineFactory( ChannelHandler handler )
+    {
+        this.handler = handler;
+    }
+    
+    public ChannelPipeline getPipeline() throws Exception
+    {
+        ChannelPipeline pipeline = Channels.pipeline(); 
+        // Add the text line codec combination first
+        pipeline.addLast( "framer", new DelimiterBasedFrameDecoder( 8192, delimiters ) );  
+        pipeline.addLast( "stringDecoder", new StringDecoder() );
+        pipeline.addLast( "encoder", new StringEncoder() );
+        pipeline.addLast( "eslMessageDecoder", new EslMessageDecoder() );
+        // Add an executor to ensure separate thread for each upstream message from here
+        pipeline.addLast( "executor", new ExecutionHandler( 
+            new OrderedMemoryAwareThreadPoolExecutor( 16, 1048576, 1048576 ) ) );
+
+        // now the inbound client logic
+        pipeline.addLast( "clientHandler", handler );
+        
+        return pipeline;
+    }
+
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/inbound/InboundPipelineFactory.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/AbstractEslClientHandler.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/AbstractEslClientHandler.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/AbstractEslClientHandler.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.internal;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Value;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Specialised {@link ChannelUpstreamHandler} that implements the logic of an ESL connection that
+ * is common to both inbound and outbound clients. This 
+ * handler expects to receive decoded {@link EslMessage} or {@link EslEvent} objects. The key 
+ * responsibilities for this class are:
+ * </p>
+ * * To synthesise a synchronous command/response api.  All IO operations using the underlying Netty 
+ * library are intrinsically asynchronous which provides for excellent response and scalability.  This 
+ * class provides for a blocking wait mechanism for responses to commands issued to the server.  A 
+ * key assumption here is that the FreeSWITCH server will process synchronous requests in the order they
+ * are received.
+ * </p>
+ * * Concrete sub classes are expected to 'terminate' the Netty IO processing pipeline (ie be the 'last'
+ * handler). 
+ * </p>
+ * Note: implementation requirement is that an {@link ExecutionHandler} is placed in the processing 
+ * pipeline prior to this handler. This will ensure that each incoming message is processed in its
+ * own thread (although still guaranteed to be processed in the order of receipt).
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public abstract class AbstractEslClientHandler extends SimpleChannelUpstreamHandler
+{
+    public static final String MESSAGE_TERMINATOR = "\n\n";  
+    public static final String LINE_TERMINATOR = "\n";  
+
+    protected final Logger log = LoggerFactory.getLogger( this.getClass() );
+
+    private final Lock syncLock = new ReentrantLock();
+    private final Queue<SyncCallback> syncCallbacks = new ConcurrentLinkedQueue<SyncCallback>();
+
+    @Override
+    public void messageReceived( ChannelHandlerContext ctx, MessageEvent e ) throws Exception
+    {
+        if ( e.getMessage() instanceof EslMessage )
+        {
+            EslMessage message = (EslMessage)e.getMessage();
+            String contentType = message.getContentType();
+            if ( contentType.equals( Value.TEXT_EVENT_PLAIN ) ||
+                    contentType.equals( Value.TEXT_EVENT_XML ) )
+            {
+                //  transform into an event
+                EslEvent eslEvent = new EslEvent( message );
+                handleEslEvent( ctx, eslEvent );
+            }
+            else
+            {
+                handleEslMessage( ctx, (EslMessage)e.getMessage() );
+            }
+        }
+        else
+        {
+            throw new IllegalStateException( "Unexpected message type: " + e.getMessage().getClass() );
+        }
+    }
+    
+    /**
+     * Synthesise a synchronous command/response by creating a callback object which is placed in 
+     * queue and blocks waiting for another IO thread to process an incoming {@link EslMessage} and
+     * attach it to the callback.
+     * 
+     * @param channel
+     * @param command single string to send
+     * @return the {@link EslMessage} attached to this command's callback
+     */
+    public EslMessage sendSyncSingleLineCommand( Channel channel, final String command )
+    {
+        SyncCallback callback = new SyncCallback();
+        syncLock.lock();
+        try 
+        {
+            syncCallbacks.add( callback );
+            channel.write( command + MESSAGE_TERMINATOR );
+        } 
+        finally 
+        {
+            syncLock.unlock();
+        }
+        
+        //  Block until the response is available 
+        return callback.get();
+    }
+
+    /**
+     * Synthesise a synchronous command/response by creating a callback object which is placed in 
+     * queue and blocks waiting for another IO thread to process an incoming {@link EslMessage} and
+     * attach it to the callback.
+     * 
+     * @param channel
+     * @param command List of command lines to send
+     * @return the {@link EslMessage} attached to this command's callback
+     */
+    public EslMessage sendSyncMultiLineCommand( Channel channel, final List<String> commandLines )
+    {
+        SyncCallback callback = new SyncCallback();
+        //  Build command with double line terminator at the end
+        StringBuilder sb = new StringBuilder();
+        for ( String line : commandLines )
+        {
+            sb.append( line );
+            sb.append( LINE_TERMINATOR );
+        }
+        sb.append( LINE_TERMINATOR );
+        
+        syncLock.lock();
+        try 
+        {
+            syncCallbacks.add( callback );
+            channel.write( sb.toString() );
+        } 
+        finally 
+        {
+            syncLock.unlock();
+        }
+        
+        //  Block until the response is available 
+        return callback.get();
+    }
+
+    /**
+     * Returns the Job UUID of that the response event will have.
+     * 
+     * @param channel
+     * @param command
+     * @return Job-UUID as a string
+     */
+    public String sendAsyncCommand( Channel channel, final String command )
+    {
+        /*
+         * Send synchronously to get the Job-UUID to return, the results of the actual 
+         * job request will be returned by the server as an async event.
+         */
+        EslMessage response = sendSyncSingleLineCommand( channel, command );
+        if ( response.hasHeader( Name.JOB_UUID ) )
+        {
+            return response.getHeaderValue( Name.JOB_UUID );
+        }
+        else
+        {
+            throw new IllegalStateException( "Missing Job-UUID header in bgapi response" );
+        }
+    }
+    
+    protected void handleEslMessage( ChannelHandlerContext ctx, EslMessage message )
+    {
+        log.info( "Received message: [{}]", message );
+        String contentType = message.getContentType();
+        
+        if ( contentType.equals( Value.API_RESPONSE ) )
+        {
+            log.debug( "Api response received [{}]", message );
+            syncCallbacks.poll().handle( message );
+        }
+        else if ( contentType.equals( Value.COMMAND_REPLY ) )
+        {
+            log.debug( "Command reply received [{}]", message );
+            syncCallbacks.poll().handle( message );
+        }
+        else if ( contentType.equals( Value.AUTH_REQUEST ) )
+        {
+            log.debug( "Auth request received [{}]", message );
+            handleAuthRequest( ctx );
+        }
+        else if ( contentType.equals( Value.TEXT_DISCONNECT_NOTICE ) )
+        {
+            log.debug( "Disconnect notice received [{}]", message );
+            handleDisconnectionNotice();
+        }
+        else
+        {
+            log.warn( "Unexpected message content type [{}]", contentType );
+        }
+    }
+
+    protected abstract void handleEslEvent( ChannelHandlerContext ctx, EslEvent event );
+
+    protected abstract void handleAuthRequest( ChannelHandlerContext ctx );
+
+    protected abstract void handleDisconnectionNotice();
+    
+    private static class SyncCallback
+    {
+        private static final Logger log = LoggerFactory.getLogger( SyncCallback.class );
+        private final CountDownLatch latch = new CountDownLatch( 1 );
+        private EslMessage response;
+
+        /**
+         * Block waiting for the countdown latch to be released, then return the 
+         * associated response object.
+         * @return
+         */
+        EslMessage get()
+        {
+            try
+            {
+                log.trace( "awaiting latch ... " );
+                latch.await();
+            }
+            catch ( InterruptedException e )
+            {
+                throw new RuntimeException( e );
+            }
+            
+            log.trace( "returning response [{}]", response );
+            return response;
+        }
+
+        /**
+         * Attach this response to the callback and release the countdown latch.
+         * @param response
+         */
+        void handle( EslMessage response )
+        {
+            this.response = response;
+            log.trace( "releasing latch for response [{}]", response );
+            latch.countDown();
+        }
+    }
+
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/AbstractEslClientHandler.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/HeaderParser.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/HeaderParser.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/HeaderParser.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.internal;
+
+/**
+ * This parser provides a static helper method to split a standard Header field
+ * into the name and value parts.
+ * <p/>
+ * This code was copied from the splitHeader() method in the 
+ * <code>org.jboss.netty.handler.codec.http.HttpMessageDecoder</code> class, which
+ * is licensed under the Apache License version 2. Original author: Trustin Lee.
+ *  
+ * @author	 Trustin Lee
+ * @author   david varnes
+ */
+public class HeaderParser
+{
+    /**
+     * Split a header in the form 
+     * <pre>
+     *   Header-Name: Some_header-value
+     * </pre>
+     * into a String array.
+     * 
+     * @param  sb the string header to parse
+     * @return a String[] array with header name at 0 and header value at 1
+     */
+    public static String[] splitHeader(String sb) {
+        final int length = sb.length();
+        int nameStart;
+        int nameEnd;
+        int colonEnd;
+        int valueStart;
+        int valueEnd;
+
+        nameStart = findNonWhitespace(sb, 0);
+        for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
+            char ch = sb.charAt(nameEnd);
+            if (ch == ':' || Character.isWhitespace(ch)) {
+                break;
+            }
+        }
+
+        for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
+            if (sb.charAt(colonEnd) == ':') {
+                colonEnd ++;
+                break;
+            }
+        }
+
+        valueStart = findNonWhitespace(sb, colonEnd);
+        if (valueStart == length) {
+            return new String[] {
+                    sb.substring(nameStart, nameEnd),
+                    ""
+            };
+        }
+
+        valueEnd = findEndOfString(sb);
+        return new String[] {
+                sb.substring(nameStart, nameEnd),
+                sb.substring(valueStart, valueEnd)
+        };
+    }
+
+    private static int findNonWhitespace(String sb, int offset) {
+        int result;
+        for (result = offset; result < sb.length(); result ++) {
+            if (!Character.isWhitespace(sb.charAt(result))) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    private static int findEndOfString(String sb) {
+        int result;
+        for (result = sb.length(); result > 0; result --) {
+            if (!Character.isWhitespace(sb.charAt(result - 1))) {
+                break;
+            }
+        }
+        return result;
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/HeaderParser.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/IEslProtocolListener.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/IEslProtocolListener.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/IEslProtocolListener.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.internal;
+
+import org.freeswitch.esl.client.inbound.Client;
+import org.freeswitch.esl.client.transport.CommandResponse;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+
+/**
+ * End users of the {@link Client} should not need to use this class. 
+ * </p>
+ * Allow client implementations to observe events arriving from the server.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public interface IEslProtocolListener
+{
+    void authResponseReceived( CommandResponse response );
+    
+    void eventReceived( EslEvent event );
+    
+    void disconnected();
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/IEslProtocolListener.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ChannelEventRunnable.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ChannelEventRunnable.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ChannelEventRunnable.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,87 @@
+package org.freeswitch.esl.client.internal.debug;
+/*
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * Red Hat 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.
+ */
+
+
+import java.util.concurrent.Executor;
+
+import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.util.EstimatableObjectWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * a {@link Runnable} which sends the specified {@link ChannelEvent} upstream.
+ * Most users will not see this type at all because it is used by
+ * {@link Executor} implementors only
+ *
+ * @author The Netty Project (netty-dev@lists.jboss.org)
+ * @author Trustin Lee (tlee@redhat.com)
+ *
+ * @version $Rev: 1685 $, $Date: 2009-08-28 16:15:49 +0900 (금, 28 8 2009) $
+ *
+ */
+public class ChannelEventRunnable implements Runnable, EstimatableObjectWrapper {
+    
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+    private final ChannelHandlerContext ctx;
+    private final ChannelEvent e;
+    volatile int estimatedSize;
+
+    /**
+     * Creates a {@link Runnable} which sends the specified {@link ChannelEvent}
+     * upstream via the specified {@link ChannelHandlerContext}.
+     */
+    public ChannelEventRunnable(ChannelHandlerContext ctx, ChannelEvent e) {
+        this.ctx = ctx;
+        this.e = e;
+    }
+
+    /**
+     * Returns the {@link ChannelHandlerContext} which will be used to
+     * send the {@link ChannelEvent} upstream.
+     */
+    public ChannelHandlerContext getContext() {
+        return ctx;
+    }
+
+    /**
+     * Returns the {@link ChannelEvent} which will be sent upstream.
+     */
+    public ChannelEvent getEvent() {
+        return e;
+    }
+
+    /**
+     * Sends the event upstream.
+     */
+    public void run() {
+//        log.info( "Sending [{}] upstream in [{}]", e, ctx );
+        try
+        {
+            ctx.sendUpstream(e);
+        }
+        catch ( Throwable t )
+        {
+            log.error( "Caught -->", t );
+        }
+    }
+
+    public Object unwrap() {
+        return e;
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ChannelEventRunnable.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ExecutionHandler.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ExecutionHandler.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ExecutionHandler.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,118 @@
+package org.freeswitch.esl.client.internal.debug;
+
+/*
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * Red Hat 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.
+ */
+
+import java.util.concurrent.Executor;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelDownstreamHandler;
+import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineCoverage;
+import org.jboss.netty.channel.ChannelState;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.util.ExternalResourceReleasable;
+import org.jboss.netty.util.internal.ExecutorUtil;
+
+/**
+ * Forwards an upstream {@link ChannelEvent} to an {@link Executor}.
+ * <p>
+ * You can implement various thread model by adding this handler to a
+ * {@link ChannelPipeline}.  The most common use case of this handler is to
+ * add a {@link ExecutionHandler} which was specified with
+ * {@link OrderedMemoryAwareThreadPoolExecutor}:
+ * <pre>
+ * ChannelPipeline pipeline = ...;
+ * pipeline.addLast("decoder", new MyProtocolDecoder());
+ * pipeline.addLast("encoder", new MyProtocolEncoder());
+ *
+ * // HERE
+ * <strong>pipeline.addLast("executor", new {@link ExecutionHandler}(new {@link OrderedMemoryAwareThreadPoolExecutor}(16, 1048576, 1048576)));</strong>
+ *
+ * pipeline.addLast("handler", new MyBusinessLogicHandler());
+ * </pre>
+ * to utilize more processors to handle {@link ChannelEvent}s.  You can also
+ * use other {@link Executor} implementation than the recommended
+ * {@link OrderedMemoryAwareThreadPoolExecutor}.
+ *
+ * @author The Netty Project (netty-dev@lists.jboss.org)
+ * @author Trustin Lee (tlee@redhat.com)
+ *
+ * @version $Rev: 1685 $, $Date: 2009-08-28 16:15:49 +0900 (금, 28 8 2009) $
+ *
+ * @apiviz.landmark
+ * @apiviz.has java.util.concurrent.ThreadPoolExecutor
+ */
+@ChannelPipelineCoverage("all")
+public class ExecutionHandler implements ChannelUpstreamHandler, ChannelDownstreamHandler, ExternalResourceReleasable {
+
+    private final Executor executor;
+
+    /**
+     * Creates a new instance with the specified {@link Executor}.
+     * Specify an {@link OrderedMemoryAwareThreadPoolExecutor} if unsure.
+     */
+    public ExecutionHandler(Executor executor) {
+        if (executor == null) {
+            throw new NullPointerException("executor");
+        }
+        this.executor = executor;
+    }
+
+    /**
+     * Returns the {@link Executor} which was specified with the constructor.
+     */
+    public Executor getExecutor() {
+        return executor;
+    }
+
+    /**
+     * Shuts down the {@link Executor} which was specified with the constructor
+     * and wait for its termination.
+     */
+    public void releaseExternalResources() {
+        ExecutorUtil.terminate(getExecutor());
+    }
+
+    public void handleUpstream(
+            ChannelHandlerContext context, ChannelEvent e) throws Exception {
+        executor.execute(new ChannelEventRunnable(context, e));
+    }
+
+    public void handleDownstream(
+            ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
+        if (e instanceof ChannelStateEvent) {
+            ChannelStateEvent cse = (ChannelStateEvent) e;
+            if (cse.getState() == ChannelState.INTEREST_OPS &&
+                (((Integer) cse.getValue()).intValue() & Channel.OP_READ) != 0) {
+
+                // setReadable(true) requested
+                boolean readSuspended = ctx.getAttachment() != null;
+                if (readSuspended) {
+                    // Drop the request silently if MemoryAwareThreadPool has
+                    // set the flag.
+                    e.getFuture().setSuccess();
+                    return;
+                }
+            }
+        }
+
+        ctx.sendDownstream(e);
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/internal/debug/ExecutionHandler.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundClientHandler.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundClientHandler.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundClientHandler.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound;
+
+import org.freeswitch.esl.client.internal.AbstractEslClientHandler;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipelineCoverage;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+
+/**
+ * Specialised {@link AbstractEslClientHandler} that implements the base connecction logic for an 
+ * 'Outbound' FreeSWITCH Event Socket connection.  The responsibilities for this class are:
+ * </p>
+ * * To send a 'connect' command when the FreeSWITCH server first establishes a new connection with
+ * the socket client in Outbound mode.  This will result in an incoming {@link EslMessage} that is
+ * transformed into an {@link EslEvent} that sub classes can handle.
+ * </p>
+ * Note: implementation requirement is that an {@link ExecutionHandler} is placed in the processing 
+ * pipeline prior to this handler. This will ensure that each incoming message is processed in its
+ * own thread (although still guaranteed to be processed in the order of receipt).
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+@ChannelPipelineCoverage( "one" )
+public abstract class AbstractOutboundClientHandler extends AbstractEslClientHandler
+{
+
+    @Override
+    public void channelConnected( ChannelHandlerContext ctx, ChannelStateEvent e ) throws Exception
+    {
+        // Have received a connection from FreeSWITCH server, send connect response
+        log.debug( "Received new connection from server, sending connect message" );
+        
+        EslMessage response = sendSyncSingleLineCommand( ctx.getChannel(), "connect" );
+        // A hack in the message decoder treats most of this incoming message as an event so parse now
+        EslEvent channelDataEvent = new EslEvent( response, true );
+        // Let implementing sub classes choose what to do next
+        handleConnectResponse( ctx, channelDataEvent );
+    }
+
+    protected abstract void handleConnectResponse( ChannelHandlerContext ctx, EslEvent event );
+
+    @Override
+    protected void handleAuthRequest( ChannelHandlerContext ctx )
+    {
+        // This should not happen in outbound mode
+        log.warn( "Auth request received in outbound mode, ignoring" ); 
+    }
+
+    @Override
+    protected void handleDisconnectionNotice()
+    {
+        log.debug( "Received disconnection notice" );
+    }    
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundClientHandler.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundPipelineFactory.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundPipelineFactory.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundPipelineFactory.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound;
+
+import org.freeswitch.esl.client.internal.debug.ExecutionHandler;
+import org.freeswitch.esl.client.transport.message.EslMessageDecoder;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
+import org.jboss.netty.handler.codec.string.StringDecoder;
+import org.jboss.netty.handler.codec.string.StringEncoder;
+import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
+
+/**
+ * </p>
+ * An abstract factory to assemble a Netty processing pipeline for outbound clients.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public abstract class AbstractOutboundPipelineFactory implements ChannelPipelineFactory
+{
+    
+    private final ChannelBuffer[] delimiters = 
+        new ChannelBuffer[] { ChannelBuffers.wrappedBuffer( new byte[] { '\n' } ),
+                              ChannelBuffers.wrappedBuffer( new byte[] { '\n', '\n' } ) };
+    
+    public ChannelPipeline getPipeline() throws Exception
+    {
+        ChannelPipeline pipeline = Channels.pipeline(); 
+        // Add the text line codec combination first
+        pipeline.addLast( "framer", new DelimiterBasedFrameDecoder( 8192, delimiters ) );  
+        pipeline.addLast( "stringDecoder", new StringDecoder() );
+        pipeline.addLast( "encoder", new StringEncoder() );
+        pipeline.addLast( "eslMessageDecoder", new EslMessageDecoder( true ) );
+        // Add an executor to ensure separate thread for each upstream message from here
+        pipeline.addLast( "executor", new ExecutionHandler( 
+            new OrderedMemoryAwareThreadPoolExecutor( 16, 1048576, 1048576 ) ) );
+
+        // now the outbound client logic
+        pipeline.addLast( "clientHandler", makeHandler() );
+        
+        return pipeline;
+    }
+
+    protected abstract AbstractOutboundClientHandler makeHandler();
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/AbstractOutboundPipelineFactory.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/SocketClient.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/SocketClient.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/SocketClient.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executors;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.ChannelGroupFuture;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Entry point to run a socket client that a running FreeSWITCH Event Socket Library module can
+ * make outbound connections to.
+ * </p>
+ * This class provides for what the FreeSWITCH documentation refers to as 'Outbound' connections
+ * from the Event Socket module. That is, with reference to the module running on the FreeSWITCH
+ * server, this client accepts an outbound connection from the server module.
+ * </p>
+ * See http://wiki.freeswitch.org/wiki/Mod_event_socket
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class SocketClient
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+
+    private final ChannelGroup allChannels = new DefaultChannelGroup( "esl-socket-client" );
+    
+    private final int port;
+    private final ChannelFactory channelFactory;
+    private final AbstractOutboundPipelineFactory pipelineFactory;
+    
+    public SocketClient( int port, AbstractOutboundPipelineFactory pipelineFactory )
+    {
+        this.port = port;
+        this.pipelineFactory = pipelineFactory;
+        this.channelFactory =  new NioServerSocketChannelFactory( 
+            Executors.newCachedThreadPool(), 
+            Executors.newCachedThreadPool() ); 
+    }
+    
+    public void start()
+    {
+        ServerBootstrap bootstrap = new ServerBootstrap( channelFactory );
+        
+        bootstrap.setPipelineFactory( pipelineFactory );
+        bootstrap.setOption( "child.tcpNoDelay", true );
+        bootstrap.setOption( "child.keepAlive", true );
+        
+        Channel serverChannel = bootstrap.bind( new InetSocketAddress( port ) );
+        allChannels.add( serverChannel );
+        log.info( "SocketClient waiting for connections on port [{}] ...", port );
+    }
+    
+    public void stop()
+    {
+        ChannelGroupFuture future = allChannels.close();
+        future.awaitUninterruptibly();
+        channelFactory.releaseExternalResources();
+        log.info( "SocketClient stopped" );
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/SocketClient.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupOutboundHandler.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupOutboundHandler.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupOutboundHandler.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound.example;
+
+import org.freeswitch.esl.client.outbound.AbstractOutboundClientHandler;
+import org.freeswitch.esl.client.transport.SendMsg;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+
+/**
+ * Simple example of a handler for outbound connection from FreeSWITCH server.
+ * This class will log some of the FreeSWTICH call channel variables and 
+ * then hangup the call.  
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class SimpleHangupOutboundHandler extends AbstractOutboundClientHandler
+{
+    
+    @Override
+    protected void handleConnectResponse( ChannelHandlerContext ctx, EslEvent event )
+    {
+        log.info( "Received connect response [{}]", event );
+        if ( event.getEventName().equalsIgnoreCase( "CHANNEL_DATA" ) )
+        {
+            // this is the response to the initial connect 
+            log.info( "=======================  incoming channel data  =============================" );
+            log.info( "Event-Date-Local: [{}]", event.getEventDateLocal() );
+            log.info( "Unique-ID: [{}]", event.getEventHeaders().get( "Unique-ID" ) );
+            log.info( "Channel-ANI: [{}]", event.getEventHeaders().get( "Channel-ANI" ) );
+            log.info( "Answer-State: [{}]", event.getEventHeaders().get( "Answer-State" ) );
+            log.info( "Caller-Destination-Number: [{}]", event.getEventHeaders().get( "Caller-Destination-Number" ) );
+            log.info( "=======================  = = = = = = = = = = =  =============================" );
+            
+            // now hangup the call
+            hangupCall( ctx.getChannel() );
+        }
+        else
+        {
+            throw new IllegalStateException( "Unexpected event after connect: [" + event.getEventName() + ']' );
+        }
+    }
+
+    @Override
+    protected void handleEslEvent( ChannelHandlerContext ctx, EslEvent event )
+    {
+        log.info( "Received event [{}]", event );
+    }
+    
+    private void hangupCall( Channel channel )
+    {
+        SendMsg hangupMsg = new SendMsg();
+        hangupMsg.addCallCommand( "execute" );
+        hangupMsg.addExecuteAppName( "hangup" );
+        
+        EslMessage response = sendSyncMultiLineCommand( channel, hangupMsg.getMsgLines() );
+        
+        if ( response.getHeaderValue( Name.REPLY_TEXT ).startsWith( "+OK" ) )
+        {
+            log.info( "Call hangup successful" );
+        }
+        else
+        {
+            
+        }
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupOutboundHandler.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupPipelineFactory.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupPipelineFactory.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupPipelineFactory.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound.example;
+
+import org.freeswitch.esl.client.outbound.AbstractOutboundClientHandler;
+import org.freeswitch.esl.client.outbound.AbstractOutboundPipelineFactory;
+
+/**
+ * Factory for the simple hangup handler
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class SimpleHangupPipelineFactory extends AbstractOutboundPipelineFactory
+{
+
+    @Override
+    protected AbstractOutboundClientHandler makeHandler()
+    {
+        return new SimpleHangupOutboundHandler();
+    }
+
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/outbound/example/SimpleHangupPipelineFactory.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/CommandResponse.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/CommandResponse.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/CommandResponse.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport;
+
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+
+/**
+ * Result object to carry the results of a command sent to the FreeSWITCH Event Socket.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class CommandResponse
+{
+    private final String command;
+    private final String replyText;
+    private final EslMessage response;
+    private final boolean success;
+    
+    public CommandResponse( String command, EslMessage response )
+    {
+        this.command = command;
+        this.response = response;
+        this.replyText = response.getHeaderValue( Name.REPLY_TEXT );
+        this.success = replyText.startsWith( "+OK" );
+    }
+    
+    /**
+     * @return the original command sent to the server
+     */
+    public String getCommand()
+    {
+        return command;
+    }
+    
+    /**
+     * @return true if and only if the response Reply-Text line starts with "+OK"
+     */
+    public boolean isOk()
+    {
+        return success;
+    }
+    
+    /**
+     * @return the full response Reply-Text line.
+     */
+    public String getReplyText()
+    {
+        return replyText;
+    }
+    
+    /**
+     * @return {@link EslMessage} the full response from the server 
+     */
+    public EslMessage getResponse()
+    {
+        return response;
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/CommandResponse.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/SendMsg.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/SendMsg.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/SendMsg.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,64 @@
+package org.freeswitch.esl.client.transport;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SendMsg
+{
+    private final List<String> msgLines = new ArrayList<String>();
+    
+    public SendMsg()
+    {
+        msgLines.add( "sendmsg" );
+    }
+    
+    public SendMsg( String uuid )
+    {
+        msgLines.add( "sendmsg " + uuid );
+    }
+    
+    public void addCallCommand( String command )
+    {
+        msgLines.add( "call-command: " + command );
+    }
+    
+    public void addExecuteAppName( String appName )
+    {
+        msgLines.add( "execute-app-name: " + appName );
+    }
+    
+    public void addExecuteAppArg( String arg )
+    {
+        msgLines.add( "execute-app-arg: " + arg );
+    }
+    
+    public void addLoops( int count )
+    {
+        msgLines.add( "loops: " + count );
+    }
+
+    public void addHangupCause( String cause )
+    {
+        msgLines.add( "hangup-cause: " + cause );
+    }
+
+    public void addNomediaUuid( String info )
+    {
+        msgLines.add( "nomedia-uuid: " + info );
+    }
+    
+    public void addEventLock()
+    {
+        msgLines.add( "event-lock: true" );
+    }
+    
+    public void addGenericLine( String name, String value )
+    {
+        msgLines.add( name + ": " + value );
+    }
+    
+    public List<String> getMsgLines()
+    {
+        return msgLines;
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/SendMsg.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEvent.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEvent.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEvent.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport.event;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.freeswitch.esl.client.internal.HeaderParser;
+import org.freeswitch.esl.client.transport.message.EslHeaders;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Value;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FreeSWITCH Event Socket events are decoded into this data object.
+ * </p>
+ * An ESL event is modelled as a collection of text lines. An event always has several eventHeader
+ * lines, and optionally may have some eventBody lines.  In addition the messageHeaders of the 
+ * original containing {@link EslMessage} which carried the event are also available. 
+ * </p>
+ * The eventHeader lines are parsed and cached in a map keyed by the eventHeader name string. An event
+ * is always expected to have an "Event-Name" eventHeader. Commonly used eventHeader names are coded
+ * in {@link EslEventHeaderNames}</br>
+ * Any eventBody lines are cached in a list.</br>
+ * The messageHeader lines from the original message are cached in a map keyed by {@link EslHeaders.Name}.
+ *  
+ * @author  david varnes
+ * @version $Id$
+ * @see EslEventHeaderNames
+ */
+public class EslEvent 
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+    
+    private final Map<Name,String> messageHeaders;
+    private final Map<String,String> eventHeaders;
+    private final List<String> eventBody;
+    private boolean decodeEventHeaders = true;
+    
+    public EslEvent( EslMessage rawMessage )
+    {
+        this( rawMessage, false );
+    }
+    
+    public EslEvent( EslMessage rawMessage, boolean parseCommandReply )
+    {
+        messageHeaders = rawMessage.getHeaders();
+        eventHeaders = new HashMap<String,String>( rawMessage.getBodyLines().size() );
+        eventBody = new ArrayList<String>();
+        // plain or xml body
+        if ( rawMessage.getContentType().equals( Value.TEXT_EVENT_PLAIN ) )
+        {
+            parsePlainBody( rawMessage.getBodyLines() );
+        }
+        else if ( rawMessage.getContentType().equals( Value.TEXT_EVENT_XML ) )
+        {
+            throw new IllegalStateException( "XML events are not yet supported" );
+        }
+        else if ( rawMessage.getContentType().equals( Value.COMMAND_REPLY ) && parseCommandReply )
+        {
+            parsePlainBody( rawMessage.getBodyLines() );
+        }
+        else
+        {
+            throw new IllegalStateException( "Unexpected EVENT content-type: " + 
+                rawMessage.getContentType() );
+        }
+    }
+
+    /**
+     * The message headers of the original ESL message from which this event was decoded.
+     * The message headers are stored in a map keyed by {@link EslHeaders.Name}. The string mapped value 
+     * is the parsed content of the header line (ie, it does not include the header name).
+     *  
+     * @return map of header values
+     */
+    public Map<Name,String> getMessageHeaders()
+    {
+        return messageHeaders;
+    }
+
+    /**
+     * The event headers of this event. The headers are parsed and stored in a map keyed by the string 
+     * name of the header, and the string mapped value is the parsed content of the event header line 
+     * (ie, it does not include the header name).
+     *  
+     * @return map of event header values
+     */
+    public Map<String, String> getEventHeaders()
+    {
+        return eventHeaders;
+    }
+    
+    /**
+     * Any event body lines that were present in the event.
+     * 
+     * @return list of decoded event body lines, may be an empty list.
+     */
+    public List<String> getEventBodyLines()
+    {
+        return eventBody;
+    }
+    
+    /**
+     * Convenience method.
+     * 
+     * @return the string value of the event header "Event-Name"
+     */
+    public String getEventName()
+    {
+        return getEventHeaders().get( EslEventHeaderNames.EVENT_NAME ); 
+    }
+    
+    /**
+     * Convenience method.
+     * 
+     * @return long value of the event header "Event-Date-Timestamp"
+     */
+    public long getEventDateTimestamp()
+    {
+        return Long.valueOf( getEventHeaders().get( EslEventHeaderNames.EVENT_DATE_TIMESTAMP ) );
+    }
+
+    /**
+     * Convenience method.
+     * 
+     * @return long value of the event header "Event-Date-Local"
+     */
+    public String getEventDateLocal()
+    {
+        return getEventHeaders().get( EslEventHeaderNames.EVENT_DATE_LOCAL );
+    }
+
+    /**
+     * Convenience method.
+     * 
+     * @return long value of the event header "Event-Date-GMT"
+     */
+    public String getEventDateGmt()
+    {
+        return getEventHeaders().get( EslEventHeaderNames.EVENT_DATE_GMT );
+    }
+
+    /**
+     * Convenience method.
+     * 
+     * @return true if the eventBody list is not empty.
+     */
+    public boolean hasEventBody()
+    {
+        return ! eventBody.isEmpty();
+    }
+    
+    private void parsePlainBody( final List<String> rawBodyLines )
+    {
+        boolean isEventBody = false;
+        for ( String rawLine : rawBodyLines )
+        {
+            if ( ! isEventBody )
+            {
+                // split the line
+                String[] headerParts = HeaderParser.splitHeader( rawLine );
+                if ( decodeEventHeaders )
+                {
+                    try
+                    {
+                        String decodedValue = URLDecoder.decode( headerParts[1], "UTF-8" );
+                        log.trace( "decoded from: [{}]", headerParts[1] );
+                        log.trace( "decoded   to: [{}]", decodedValue );
+                        eventHeaders.put( headerParts[0], decodedValue );
+                    }
+                    catch ( UnsupportedEncodingException e )
+                    {
+                        log.warn( "Could not URL decode [{}]", headerParts[1] );
+                        eventHeaders.put( headerParts[0], headerParts[1] );
+                    }
+                }
+                else
+                {
+                    eventHeaders.put( headerParts[0], headerParts[1] );
+                }
+                if ( headerParts[0].equals( EslEventHeaderNames.CONTENT_LENGTH ) )
+                {
+                    // the remaining lines will be considered body lines
+                    isEventBody = true;
+                }
+            }
+            else
+            {
+                // ignore blank line (always is one following the content-length
+                if ( rawLine.length() > 0 )
+                {
+                    eventBody.add( rawLine );
+                }
+            }
+        }
+
+    }
+    
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder( "EslEvent: name=[" );
+        sb.append( getEventName() );
+        sb.append( "] headers=" );
+        sb.append( messageHeaders.size() );
+        sb.append( ", eventHeaders=" );
+        sb.append( eventHeaders.size() );
+        sb.append( ", eventBody=" );
+        sb.append( eventBody.size() );
+        sb.append( " lines." );
+        
+        return sb.toString();
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEvent.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEventHeaderNames.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEventHeaderNames.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEventHeaderNames.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport.event;
+
+/**
+ * Container class for some commonly used ESL event header names (note there are many more!).
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class EslEventHeaderNames
+{
+    /**
+     * {@code "Event-Name"}
+     */
+    public static final String EVENT_NAME = "Event-Name";
+    /**
+     * {@code "Event-Date-Local"}
+     */
+    public static final String EVENT_DATE_LOCAL = "Event-Date-Local";
+    /**
+     * {@code "Event-Date-GMT"}
+     */
+    public static final String EVENT_DATE_GMT = "Event-Date-GMT";
+    /**
+     * {@code "Event-Date-Timestamp"}
+     */
+    public static final String EVENT_DATE_TIMESTAMP = "Event-Date-Timestamp";
+    /**
+     * {@code "Event-Calling-File"}
+     */
+    public static final String EVENT_CALLING_FILE = "Event-Calling-File";
+    /**
+     * {@code "Event-Calling-Function"}
+     */
+    public static final String EVENT_CALLING_FUNCTION = "Event-Calling-Function";
+    /**
+     * {@code "Event-Calling-Line-Number"}
+     */
+    public static final String EVENT_CALLING_LINE_NUMBER = "Event-Calling-Line-Number";
+    /**
+     * {@code "FreeSWITCH-Hostname"}
+     */
+    public static final String FREESWITCH_HOSTNAME = "FreeSWITCH-Hostname";
+    /**
+     * {@code "FreeSWITCH-IPv4"}
+     */
+    public static final String FREESWITCH_IPV4 = "FreeSWITCH-IPv4";
+    /**
+     * {@code "FreeSWITCH-IPv6"}
+     */
+    public static final String FREESWITCH_IPV6 = "FreeSWITCH-IPv6";
+    /**
+     * {@code "Core-UUID"}
+     */
+    public static final String CORE_UUID = "Core-UUID";
+    /**
+     * {@code "Content-Length"}
+     */
+    public static final String CONTENT_LENGTH = "Content-Length";
+    /**
+     * {@code "Job-Command"}
+     */
+    public static final String JOB_COMMAND = "Job-Command";
+    /**
+     * {@code "Job-UUID"}
+     */
+    public static final String JOB_UUID = "Job-UUID";
+
+    private EslEventHeaderNames()
+    {
+        /* private class */
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/event/EslEventHeaderNames.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslHeaders.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslHeaders.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslHeaders.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport.message;
+
+/**
+ * Container class for enumeration of ESL message header names, and some commonly used 
+ * header string values.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+public class EslHeaders
+{
+    /**
+     * Standard ESL header names.
+     * </p>
+     * Note this enum will need to be kept in synch with any new headers introduced on the server side.
+     * 
+     * @author david varnes 
+     * @version $Id$
+     */
+    public enum Name
+    {
+        /*
+         *  Minor optimization - put most often used headers at the top for fastest resolution
+         *  in static fromLiteral().  
+         */
+        
+        /**
+         * {@code "Content-Type"}
+         */
+        CONTENT_TYPE( "Content-Type" ),
+        /**
+         * {@code "Content-Length"}
+         */
+        CONTENT_LENGTH( "Content-Length" ),
+        /**
+         * {@code "Reply-Text"}
+         */
+        REPLY_TEXT( "Reply-Text" ),
+        /**
+         * {@code "Job-UUID"}
+         */
+        JOB_UUID( "Job-UUID" ),
+        /**
+         * {@code "Socket-Mode"}
+         */
+        SOCKET_MODE( "Socket-Mode" ),
+        /**
+         * {@code "Control"}
+         */
+        Control( "Control" ),
+        ;
+        
+        private final String literal;
+        
+        private Name( String literal )
+        {
+            this.literal = literal;
+        }
+        
+        public String literal()
+        {
+            return literal;
+        }
+        
+        public static Name fromLiteral( String literal )
+        {
+            for ( Name name : values() )
+            {
+                if ( name.literal.equalsIgnoreCase( literal ) )
+                {
+                    return name;
+                }
+            }
+            
+            return null;
+        }
+    }
+    
+    /**
+     * Some common ESL header values. These are provided as a convenience for commonly used values.
+     * </p>
+     * This values are not coded as an enum to allow for the very large range of possible values,
+     * since they are just Strings.
+     *
+     * @author david varnes
+     * @version $Id$
+     */
+    public static final class Value 
+    {
+        /**
+         * {@code "+OK"}
+         */
+        public static final String OK = "+OK";
+        /**
+         * {@code "auth/request"}
+         */
+        public static final String AUTH_REQUEST = "auth/request";
+        /**
+         * {@code "api/response"}
+         */
+        public static final String API_RESPONSE = "api/response";
+        /**
+         * {@code "command/reply"}
+         */
+        public static final String COMMAND_REPLY = "command/reply";
+        /**
+         * {@code "text/event-plain"}
+         */
+        public static final String TEXT_EVENT_PLAIN = "text/event-plain";
+        /**
+         * {@code "text/event-xml"}
+         */
+        public static final String TEXT_EVENT_XML = "text/event-xml";
+        /**
+         * {@code "text/disconnect-notice"}
+         */
+        public static final String TEXT_DISCONNECT_NOTICE = "text/disconnect-notice";
+        /**
+         * {@code "-ERR invalid"}
+         */
+        public static final String ERR_INVALID = "-ERR invalid";
+    }
+    
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslHeaders.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessage.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessage.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessage.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport.message;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic FreeSWITCH Event Socket messages from the server are decoded into this data object.
+ * </p>
+ * An ESL message is modelled as text lines.  A message always has one or more header lines, and 
+ * optionally may have some body lines.
+ * <p/>
+ * Header lines are parsed and cached in a map keyed by the {@link EslHeaders.Name} enum.  A message
+ * is always expected to have a "Content-Type" header</br>
+ * Any Body lines are cached in a list.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ * @see EslHeaders.Name
+ */
+public class EslMessage
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+    
+    private final Map<Name,String> headers = new HashMap<Name,String>();
+    private final List<String> body = new ArrayList<String>();
+    
+    private Integer contentLength = null;
+    private int receivedContentLength = 0;
+
+    /**
+     * All the received message headers in a map keyed by {@link EslHeaders.Name}. The string mapped value 
+     * is the parsed content of the header line (ie, it does not include the header name).
+     *  
+     * @return map of header values
+     */
+    public Map<Name,String> getHeaders()
+    {
+        return headers;
+    }
+
+    /**
+     * Convenience method
+     * 
+     * @param headerName as a {@link EslHeaders.Name}
+     * @return true if an only if there is a header entry with the supplied header name 
+     */
+    public boolean hasHeader( Name headerName )
+    {
+        return headers.containsKey( headerName );
+    }
+
+    /**
+     * Convenience method
+     * 
+     * @param headerName as a {@link EslHeaders.Name}
+     * @return same as getHeaders().get( headerName )
+     */
+    public String getHeaderValue( Name headerName )
+    {
+        return headers.get( headerName );
+    }
+    
+    /**
+     * Convenience method
+     * 
+     * @return true if and only if a header exists with name "Content-Length" 
+     */
+    public boolean hasContentLength()
+    {
+        return headers.containsKey( Name.CONTENT_LENGTH );
+    }
+    
+    /**
+     * Convenience method
+     * 
+     * @return integer value of header with name "Content-Length"
+     */
+    public Integer getContentLength()
+    {
+        if ( contentLength != null )
+        {
+            return contentLength;
+        }
+        if ( hasContentLength() )
+        {
+            contentLength = Integer.valueOf( headers.get( Name.CONTENT_LENGTH) );
+        }
+        return contentLength;
+    }
+    
+    /**
+     * Convenience method
+     * 
+     * @return header value of header with name "Content-Type"
+     */
+    public String getContentType()
+    {
+        return headers.get( Name.CONTENT_TYPE );
+    }
+
+    /**
+     * Any received message body lines
+     * 
+     * @return list with a string for each line received, may be an empty list
+     */
+    public List<String> getBodyLines()
+    {
+        return body;
+    }
+    
+    /**
+     * Used by the {@link EslMessageDecoder}.
+     * 
+     * @param name
+     * @param value
+     */
+    void addHeader( Name name, String value )
+    {
+        log.debug( "adding header [{}] [{}]", name, value );
+        headers.put( name, value );
+    }
+    
+    /**
+     * Used by the {@link EslMessageDecoder}
+     * 
+     * @param line
+     */
+    void addBodyLine( String line )
+    {
+        if ( line == null )
+        {
+            return;
+        }
+        receivedContentLength += ( line.length() + 1 );
+        // ignore empty lines (signals the end of a body section)
+        if ( line.length() > 0 )
+        {
+            body.add( line );
+        }
+    }
+
+    /**
+     * Used by the {@link EslMessageDecoder}
+     * 
+     * @return true if receivedContentLength < contentLength
+     */
+    boolean waitingForContent()
+    {
+        log.debug( "received [{}], expecting [{}]", receivedContentLength, getContentLength() );
+        return receivedContentLength < getContentLength();
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder( "EslMessage: contentType=[" );
+        sb.append( getContentType() );
+        sb.append( "] headers=" );
+        sb.append( headers.size() );
+        sb.append( ", body=" );
+        sb.append( body.size() );
+        sb.append( " lines." );
+        
+        return sb.toString();
+    }
+    
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessage.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessageDecoder.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessageDecoder.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessageDecoder.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.transport.message;
+
+import org.freeswitch.esl.client.internal.HeaderParser;
+import org.freeswitch.esl.client.transport.message.EslHeaders.Name;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipelineCoverage;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.channel.UpstreamMessageEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Decoder used by the IO processing pipeline. Client consumers should never need to use
+ * this class.
+ * 
+ * @author  david varnes
+ * @version $Id$
+ */
+@ChannelPipelineCoverage( "one" )
+public class EslMessageDecoder extends SimpleChannelUpstreamHandler
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+    
+    private EslMessage currentMessage;
+    private boolean treatUnknownHeadersAsBody = false;
+    
+    public EslMessageDecoder()
+    {
+        
+    }
+    
+    public EslMessageDecoder( boolean treatUnknownHeadersAsBody )
+    {
+        this.treatUnknownHeadersAsBody = treatUnknownHeadersAsBody;
+    }
+    
+    private enum State
+    {
+        NEW_MESSAGE,
+        READ_HEADER_LINE,
+        READ_BODY_LINE,
+    }
+    
+    private State state = State.NEW_MESSAGE;
+    
+    @Override
+    public void messageReceived( ChannelHandlerContext ctx, MessageEvent e ) throws Exception
+    {
+        // Assume this is a decoded string
+        String messageLine = e.getMessage().toString();
+        
+        log.debug( "Received string: [{}]", e.getMessage() );
+        log.trace( "State [{}]", state );
+        
+        switch ( state )
+        {
+        case NEW_MESSAGE:
+            if ( messageLine.isEmpty() )
+            {
+                break;
+            }
+            currentMessage = new EslMessage();
+            state = State.READ_HEADER_LINE;
+            // fall-through  
+        case READ_HEADER_LINE:
+            if ( messageLine.isEmpty() )
+            {
+                if ( currentMessage.hasContentLength() )
+                {
+                    //  end of headers - body to come
+                    state = State.READ_BODY_LINE;
+                }
+                else
+                {
+                    // end of message
+                    state = State.NEW_MESSAGE;
+                    // send upstream
+                    UpstreamMessageEvent upstreamEvent = new UpstreamMessageEvent( e.getChannel(), 
+                        currentMessage, e.getRemoteAddress() );
+                    currentMessage = null;
+                    
+                    ctx.sendUpstream( upstreamEvent );
+                }
+            }
+            else
+            {
+                // split the line
+                String[] headerParts = HeaderParser.splitHeader( messageLine );
+                Name headerName = Name.fromLiteral( headerParts[0] );
+                if ( headerName == null )
+                {
+                    if ( treatUnknownHeadersAsBody )
+                    {
+                        // treat this as a body line - note that this will screw up genuine body 
+                        // content-length calculations
+                        currentMessage.addBodyLine( messageLine );
+                    }
+                    else
+                    {
+                        throw new IllegalStateException( "Unhandled ESL header [" + headerParts[0] + ']' );
+                    }
+                }
+                currentMessage.addHeader( headerName, headerParts[1] );
+            }
+            break;
+        case READ_BODY_LINE:
+            // monitor received content compared to expected content-length
+            currentMessage.addBodyLine( messageLine );
+            if ( ! currentMessage.waitingForContent() )
+            {
+                // end of message
+                state = State.NEW_MESSAGE;
+                // send upstream
+                UpstreamMessageEvent upstreamEvent = new UpstreamMessageEvent( e.getChannel(), 
+                    currentMessage, e.getRemoteAddress() );
+                currentMessage = null;
+                
+                ctx.sendUpstream( upstreamEvent );
+            }
+            break;
+        default:
+            break;
+        }
+        
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/main/java/org/freeswitch/esl/client/transport/message/EslMessageDecoder.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.inbound;
+
+import org.freeswitch.esl.client.IEslEventListener;
+import org.freeswitch.esl.client.inbound.Client;
+import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
+import org.freeswitch.esl.client.transport.event.EslEvent;
+import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientTest
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+
+    private String host = "localhost";
+    private int port = 8021;
+    private String password = "ClueCon"; 
+        
+    @Test
+    public void do_connect() throws InterruptedException
+    {
+        Client client = new Client();
+     
+        client.addEventListener( new IEslEventListener()
+        {
+            public void eventReceived( EslEvent event )
+            {
+                log.info( "Event received [{}]", event );
+            }
+            public void backgroundJobResultReceived( EslEvent event )
+            {
+                log.info( "Background job result received [{}]", event );
+            }
+            
+        } );
+        
+        log.info( "Client connecting .." );
+        try
+        {
+            client.connect( host, port, password, 2 );
+        }
+        catch ( InboundConnectionFailure e )
+        {
+            log.error( "Connect failed", e );
+            return;
+        }
+        log.info( "Client connected .." );
+        
+//      client.setEventSubscriptions( "plain", "heartbeat CHANNEL_CREATE CHANNEL_DESTROY BACKGROUND_JOB" );
+        client.setEventSubscriptions( "plain", "all" );
+        client.addEventFilter( "Event-Name", "heartbeat" );
+        client.cancelEventSubscriptions();
+        client.setEventSubscriptions( "plain", "all" );
+        client.addEventFilter( "Event-Name", "heartbeat" );
+        client.addEventFilter( "Event-Name", "channel_create" );
+        client.addEventFilter( "Event-Name", "background_job" );
+//        client.sendSyncCommand( "echo", "Foo foo bar" );
+//        client.sendSyncCommand( "originate", "sofia/internal/101@192.168.100.201! sofia/internal/102@192.168.100.201!" );
+        
+//        client.sendSyncApiCommand( "sofia status", "" );
+        String jobId = client.sendAsyncApiCommand( "status", "" );
+        log.info( "Job id [{}] for [status]", jobId );
+        client.sendSyncApiCommand( "version", "" );
+//        client.sendAsyncApiCommand( "status", "" );
+//        client.sendSyncApiCommand( "sofia status", "" );
+//        client.sendAsyncApiCommand( "status", "" );
+        EslMessage response = client.sendSyncApiCommand( "sofia status", "" );
+        log.info( "sofia status = [{}]", response.getBodyLines().get( 3 ) );
+        
+        Thread.sleep( 25000 );
+        client.close();
+    }
+
+    @Test
+    public void do_multi_connects() throws InterruptedException
+    {
+        Client client = new Client();
+        
+        log.info( "Client connecting .." );
+        try
+        {
+            client.connect( host, port, password, 2 );
+        }
+        catch ( InboundConnectionFailure e )
+        {
+            log.error( "Connect failed", e );
+            return;
+        }
+        log.info( "Client connected .." );
+        
+        log.info( "Client connecting .." );
+        try
+        {
+            client.connect( "127.0.0.1", 8021, password, 2 );
+        }
+        catch ( InboundConnectionFailure e )
+        {
+            log.error( "Connect failed", e );
+            return;
+        }
+        log.info( "Client connected .." );
+    }
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/inbound/ClientTest.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/outbound/SocketClientTest.java (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/outbound/SocketClientTest.java	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/outbound/SocketClientTest.java	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 david varnes.
+ *
+ * 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.freeswitch.esl.client.outbound;
+
+import org.freeswitch.esl.client.outbound.example.SimpleHangupPipelineFactory;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SocketClientTest
+{
+    private final Logger log = LoggerFactory.getLogger( this.getClass() );
+
+    @Test
+    public void run_client() throws InterruptedException
+    {
+        log.info( "Test starting ..." );
+
+        
+        SocketClient client = new SocketClient( 8084, new SimpleHangupPipelineFactory() );
+        
+        client.start();
+        
+        Thread.sleep( 45000 );
+
+        client.stop();
+
+        
+        log.info( "Test ended" );
+    }
+    
+}
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/java/org/freeswitch/esl/client/outbound/SocketClientTest.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/resources/logback-test.xml (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/resources/logback-test.xml	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/resources/logback-test.xml	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2010 david varnes.
+ *
+ * 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.
+-->
+<configuration>
+
+  <appender name="STDOUT"
+   class="ch.qos.logback.core.ConsoleAppender">
+   <layout class="ch.qos.logback.classic.PatternLayout">
+     <Pattern>
+        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+      </Pattern>
+    </layout>
+  </appender>
+
+  <logger name="org.freeswitch.esl.client.outbound.example">
+    <level value="debug" />
+  </logger>
+  <logger name="org.freeswitch.esl.client.inbound">
+    <level value="debug" />
+  </logger>
+  <logger name="org.freeswitch.esl.client.transport.message">
+    <level value="info" />
+  </logger>
+  <logger name="org.freeswitch.esl.client">
+    <level value="info" />
+  </logger>
+
+  <root>
+    <level value="warn" />
+    <appender-ref ref="STDOUT" />
+  </root>
+
+</configuration>
\ No newline at end of file
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client/src/test/resources/logback-test.xml
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.classpath (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.classpath	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.classpath	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.classpath
___________________________________________________________________
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.project (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.project	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.project	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.freeswitch.esl.client.example</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/.project
___________________________________________________________________
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/pom.xml (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/pom.xml	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/pom.xml	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2010 david varnes.
+ *
+ * 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.freeswitch.esl.client</groupId>
+    <artifactId>esl-client-parent</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+  </parent>
+  <artifactId>org.freeswitch.esl.client.example</artifactId>
+  <name>FreeSWITCH Event Socket Library - Java Client example usage</name>
+  <dependencies>
+    <dependency>
+      <groupId>org.freeswitch.esl.client</groupId>
+      <artifactId>org.freeswitch.esl.client</artifactId>
+      <version>0.0.1-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/org.freeswitch.esl.client.example/pom.xml
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: freeswitch/trunk/contrib/dvarnes/java/esl-client/pom.xml (0 => 16127)


--- freeswitch/trunk/contrib/dvarnes/java/esl-client/pom.xml	                        (rev 0)
+++ freeswitch/trunk/contrib/dvarnes/java/esl-client/pom.xml	2010-01-03 14:54:19 UTC (rev 16127)
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2010 david varnes.
+ *
+ * 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.freeswitch.esl.client</groupId>
+  <artifactId>esl-client-parent</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <name>FreeSWITCH Event Socket Library - Java Client project</name>
+  <url>http://www.freeswitch.org</url>
+  <description>
+    The FreeSWITCH Event Socket Library provides a TCP based control and/or
+    monitoring interface to a running switch.  This project provides a Java
+    client to the ESL.
+  </description>
+  <organization>
+    <name>FreeSWITCH.org</name>
+    <url>http://www.freeswitch.org</url>
+  </organization>
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+    </license>
+  </licenses>
+  <inceptionYear>2009</inceptionYear>
+  <scm>
+    <connection>scm:svn:http://svn.freeswitch.org/svn/freeswitch/trunk/contrib/dvarnes/java/esl-client</connection>
+    <developerConnection>scm:svn:http://svn.freeswitch.org/svn/freeswitch/trunk/contrib/dvarnes/java/esl-client</developerConnection>
+  </scm>
+  <developers>
+    <developer>
+      <id>dvarnes</id>
+      <name>david varnes</name>
+      <email>david at varnes dot net</email>
+      <timezone>GMT+10</timezone>
+    </developer>
+  </developers>
+  <modules>
+  	<module>org.freeswitch.esl.client</module>
+  	<module>org.freeswitch.esl.client.example</module>
+  </modules>
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <configuration>
+            <source>1.5</source>
+            <target>1.5</target>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <!--  every child project inherits these plugins -->
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.1</version>
+        <executions>
+          <execution>
+            <id>attach-source</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <attach>true</attach>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>  
+</project>
Property changes on: freeswitch/trunk/contrib/dvarnes/java/esl-client/pom.xml
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native