Sunday, April 28, 2013

FXTalk , a Java(FX) based gtalk / jabber client

 FXTalk is a  cross-platform rich UI chat client demonstrating the abilities of JavaFX platform.

1. Resources

1.1 Sources

The source code is in Github .

1.2 Executable bundles

1) Mac OSX(built on Lion, but works on Snowleopard, Lion and mountain lion). On Mountain lion however due to Gatekeeper intervention you need to run the app with Cntrl+Click for the 1st time and subsequently you can launch the app normally.
           DMG
           App

2) Windows (32 bit)
           Msi
           Exe


2. Addressing the short comings of Smack library


We rely on a 3rd party open source library called Smack V 3.2.2. This library though is wonderful has some shortcomings which we address by fixing the smack library and re building it to meet our needs.

1) Most proxies may not allow connecting to it via itself . So setting proxy and creating a socket to proxy will try to route through itself and fail.

  Fix:
    org.jivesoftware.smack.proxy.HTTPProxySocketFactory in the method httpProxifiedSocket() change the following line
             Socket socket = new Socket(proxyhost,proxyPort);
to
             Socket socket = new Socket(Proxy.NO_PROXY);
             socket.connect(new InetSocketAddress(proxyhost,proxyPort));
2) When creating an SSL socket behind firewall & proxy there must be provision for HTTPS socket creation with an understanding there is a proxy in between
Fix:
org.jivesoftware.smack.proxy.ProxyInfo
    1) Add new enum ProxyType.HTTPS and method forHttpsProxy()
    2) Within getSocketFactory() method create HTTPSProxySocketFactory() if the ProxyType is HTTPS.
    2) Implement your own HTTPSProxySocketFactory which creates a normal HTTP connection to the proxy and then tunnel SSL through it.
We have built our own version of Smack library. This can be contributed to the  Smack source code base.

3. Quick look at Features

  1. Supports user subscription,friend request, setting avatar(Avatar editor pops up when you click your image), changing presence, set status, signout, set default account,delete accounts,set proxy, etc.
  2. Supports File transfer (by default we enable only IBB) hence please do not try to transfer large files.
  3. Password is encrypted by key which is secure randomly generated when ran the first time. Any one who can read your home folder can read the key and hence can decrypt your password.We disable read/write/exec access for any user other than the one who runs the App hence this approach is "Safe enough" . If you still think it is not enough for you (OR) your machine is shared machine then do not save the account.
  4. Tested successfully on GTalk and Oracle(Jabber) systems.
  5. Tested on MacOSX(lion) and Win 7(and native bundles are available for download see Resources Section 6).

4. Features planned for future releases

  1. Group chat
  2. Themable chat bubbles
  3. Sock5 based file transfer.
  4. Jingle based Voice chat.

5. Snapshots

6. Native Bundles


Generating native bundles is not straight forward as the bundling skips ext from jre and hene fetures like https access will not be available. To circumvent this we use custom configuration scripts for deployment.
  • Set environment variable JAVAFX_ANT_DEBUG to true
  • Create windows script    package/windows/FXTalk-post-image.wsf
  • Create shell script for MacOSX script    package/macosx/FXTalk-post-image.sh
  • Copy your custom resources to
    package/macosx
    package/windows
    package/linux

  • Run    
    ant -lib . jfx-build-native

6.1 Windows (FXTalk-post-image.wsf)

<?xml version="1.0" ?>
<package>  
   <job id="postImage">  
    <script language="JScript">  
     <![CDATA[  
        var oFSO = new ActiveXObject("Scripting.FileSystemObject");  
        var oFolder = oFSO.getFolder(".");  
        var from1 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\sunjce_provider.jar";  
        var from2 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\access-bridge.jar";  
        var from3 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\jaccess.jar";  
        var from4 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\sunpkcs11.jar";  
        var from5 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\dnsns.jar";  
        var from6 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\localedata.jar";  
        var from7 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\sunec.jar";  
        var from8 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\sunmscapi.jar";  
        var from9 = "C:\\Program Files\\Java\\jdk1.7.0_17\\jre\\lib\\ext\\zipfs.jar";  
        var to1 = "C:\\Users\\srikalyc\\Documents\\NetBeansProjects\\FXTalk\\dist\\bundles\\FXTalk\\runtime\\jre\\lib\\ext";  
        var to2 = ".\\FXTalk\\runtime\\jre\\lib\\ext";  
        if (!oFSO.FolderExists(to1)) {  
          oFSO.CreateFolder(to1);  
        }  
        if (!oFSO.FolderExists(to2)) {  
          oFSO.CreateFolder(to2);  
        }  
        to1 += "\\";  
        to2 += "\\";  
        oFSO.CopyFile(from1, to1);  
        oFSO.CopyFile(from2, to1);  
        oFSO.CopyFile(from3, to1);  
        oFSO.CopyFile(from4, to1);  
        oFSO.CopyFile(from5, to1);  
        oFSO.CopyFile(from6, to1);  
        oFSO.CopyFile(from7, to1);  
        oFSO.CopyFile(from8, to1);  
        oFSO.CopyFile(from9, to1);  

        oFSO.CopyFile(from1, to2);  
        oFSO.CopyFile(from2, to2);  
        oFSO.CopyFile(from3, to2);  
        oFSO.CopyFile(from4, to2);  
        oFSO.CopyFile(from5, to2);  
        oFSO.CopyFile(from6, to2);  
        oFSO.CopyFile(from7, to2);  
        oFSO.CopyFile(from8, to2);  
        oFSO.CopyFile(from9, to2);  
     ]]>  
    </script>  
   </job>  
</package>              

6.2 Macosx (FXTalk-post-image.sh)

If you edit the following script in Windows there is high probability that it will cause weird failures and errors(due to \n \r characters), just recreate it on Mac OSX and redo it.
- Do not forget to change the info.plist for jdk.
#!/bin/bash

#The following are required for DMG image to have the ext folder
cd ../images/dmg.image/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib
mkdir ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/dnsns.jar ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/localedata.jar ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunec.jar ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar ext
cp -p /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/zipfs.jar ext

#The following are required to patch the .app package
mkdir /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/dnsns.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/localedata.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunec.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
cp /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext/zipfs.jar /Users/srikalyc/NetbeansProjects/FXTalk/dist/bundles/FXTalk.app/Contents/PlugIns/jdk1.7.0_17.jdk/Contents/Home/jre/lib/ext
 
 

Please leave your suggestions, comments and feedback here.

Monday, January 14, 2013

Is there no WriterOutputStream in java ?

I have been wondering why there is no WriterOutputStream available in java.io package.  I was presented with a weird situation where there was a 3rd party API we had to live with ,which accepted only OutputStream , but forced to use the available PrintWriter which deals with text unlike byte streams. If you want to do the other way round its many to one mapping . See fig 1

       


So you just put strings , integers etc into the writer and behind the scenes they are just converted to stream of bytes. But when doing bytes to text you need to know what was the Character encoding used to represent the text as bytes, most often than not it is UTF-8 , assuming this we will go ahead and write a simple WriterOutputStream which will satiate the above said need (a stream wrapper for our writer) to specific extent.



 1 package javatest;
 2 
 3 import java.io.IOException;
 4 import java.io.OutputStream;
 5 import java.io.PrintWriter;
 6 
 7 /**
 8  * This class may blow up if your stream was encoded with different character code(than UTF-8)
 9  * in which case just re implement (extend this class or write your own) with
10  * a Character Decoder for the appropriate character set.
11  * @author srikalyc
12  */
13 public class WriterOutputStream extends OutputStream {
14     private PrintWriter writer;
15 
16     public WriterOutputStream(PrintWriter writer) {
17         this.writer = writer;
18     }
19 
20     /**
21      * Because only the lower order byte is used.
22      * @param b
23      * @throws IOException 
24      */
25     @Override
26     public void write(int b) throws IOException {
27         writer.write((byte)(b & 0x000000ff));
28     }
29 
30     @Override
31     public void write(byte[] b) throws IOException {
32         writer.append(new String(b));
33         flush();
34     }
35 
36     @Override
37     public void flush() throws IOException {
38         writer.flush();
39     }
40 
41     @Override
42     public void close() throws IOException {
43         writer.close();
44     }
45 
46     
47 }
48 
 
There are other ways to go about this as well, for smaller streams you could do something like this
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //Pass it to the 3rd party method which accepts only streams
        thirdPartyClass.method(baos) ;
        writer.println(baos.toString());


The baos acts like a nexus between the stream and text worlds. This could have been more efficiently
handled with ByteBuffers etc. Apache IO has a superb collection of Classes that does similar things in more extensive ways.