Scripting on the Java platform

Using Groovy, Jython, and JRuby for Java development

1 2 3 4 5 Page 5
Page 5 of 5

A bidirectional integrated application (SMTP server)

A closer integration of Java and scripting languages often requires that Java classes be called by scripting elements and scripting elements be called by Java classes. Thus, the application will consist of a seamless, bidirectional integration of scripting-based classes and Java-based classes.

Listing 13 presents an example of  an integrated script-based application that call Java classes, and vice verse. It is a rudimentary SMTP server written in JRuby that uses a non-blocking Java network library.

Listing 13. Rudimentary SMTP server written in JRuby

include Java

RandomAccessFile = java.io.RandomAccessFile
DataConverter = Java::org.xsocket.DataConverter 
MultithreadedServer = Java::org.xsocket.stream.MultithreadedServer
IConnection = Java::org.xsocket.stream.IConnection
IConnectHandler = Java::org.xsocket.stream.IConnectHandler
IDataHandler = Java::org.xsocket.stream.IDataHandler


class TestMessageSinkManager
   def new_message_sink_channel()
      file = java.io.File.create_temp_file('smtptest', 'mail')
      return RandomAccessFile.new(file, 'rw').channel
   end
end
 

class SmtpProtocolHandler
   include IConnectHandler
   include IDataHandler

   def initialize(domain, message_sink_manager)
      @domain = domain
      @msg_sink_mgr = message_sink_manager
      @helo_pattern = Regexp.compile(/HELO.*/, Regexp::IGNORECASE)
      @mail_from_pattern = Regexp.compile(/MAIL FROM:.*/, Regexp::IGNORECASE)
      @rcpt_to_pattern = Regexp.compile(/RCPT TO:.*/, Regexp::IGNORECASE)
      @data_pattern = Regexp.compile(/DATA.*/, Regexp::IGNORECASE)
      @quit_pattern = Regexp.compile(/QUIT.*/, Regexp::IGNORECASE)
   end
  
   # new incoming (non blocking) connection
   def onConnect(nbc)
      nbc.flushmode = IConnection::FlushMode::ASYNC
      nbc.attachment = { 'state' => 'CMD', 'msg_num' => 0 }
      nbc.write("220 #{@domain} SMTP ready \r\n")
      return true
   end
  
   # data received for the (non blocking) connection
   def onData(nbc)
   
      case nbc.attachment['state']
         # message receiving mode: non-blocking streaming of the msg data 
         when 'MESSAGE'
            # some validations have to be performed by the data sink
            delimiter_found = nbc.read_available_by_delimiter("\r\n.\r\n", nbc.attachment['message_channel'])          
            if delimiter_found
               nbc.attachment['message_channel'].close()
               nbc.attachment['state'] = 'CMD'
               nbc.write("250 OK #{nbc.get_id()}.#{nbc.attachment['msg_num']} \r\n")
            end

         # smtp-command mode: perform command 
         else
            # a BufferUnderflowException will been thrown, if delimiter not found
            smtp_cmd_line = nbc.read_string_by_delimiter("\r\n")
         
            case smtp_cmd_line
               when @helo_pattern
                  nbc.write("250 #{@domain} SMTP Service \r\n")
          
               when @mail_from_pattern
                  originator = smtp_cmd_line[10,9999].strip()
                  # ...here some validations should be performed (valid address, ...) 
                  nbc.attachment['originator'] = originator
                  nbc.attachment['recipients'] = []
                  nbc.write("250 #{@originator} is syntactically correct\r\n")

               when @rcpt_to_pattern
                  rec = smtp_cmd_line[8,9999].strip()
                  # ...here some validations should be performed (max recipients, ...)
                  nbc.attachment['recipients'] = nbc.attachment['recipients'] << rec
                  nbc.write("250 #{rec} verified \r\n")  

               when @data_pattern
                  # ...here some validation should be performed (recipients set, ...)
                  nbc.attachment['state'] = 'MESSAGE'
                  nbc.attachment['msg_num'] = nbc.attachment['msg_num'] + 1
                  nbc.attachment['message_channel'] = @msg_sink_mgr.new_message_sink_channel()
                  time_stamp = "Received: FROM #{nbc.remote_address.canonical_host_name} BY #{@domain}\r\n" + 
                                  "id #{nbc.get_id()}.#{nbc.attachment['msg_num']}; " + Time.new.to_s() + "\r\n"
                  nbc.attachment['message_channel'].write(DataConverter.to_byte_buffer(time_stamp, 'US-ASCII'))                  
                  nbc.write("354 Enter message, ending with \".\"  \r\n")

               when @quit_pattern
                  nbc.write("221 SMTP service closing connection \r\n")
                  nbc.close()

               else 
                  nbc.write("500 Unrecognized command \r\n")
         
            end         
      end
      return true
   end
end

server = MultithreadedServer.new(25, SmtpProtocolHandler.new('mSrv', TestMessageSinkManager.new))
server.run()

In this application a Java-based server is instantiated, which listens for incoming SMTP network connections. The network events are handled by a JRuby-based handler. To do this, the JRuby-based handler has to implement a Java callback interface defined by the Java network library.

To implement a Java interface, the JRuby class has to declare all supported interfaces using the include<java interface> statement. Unlike a Java-based interface implementation, the return type or exceptions don't have to be defined by the JRuby-based method implementation. By performing a callback method of the handler, a Java object is passed over (as a INonBlockingConnection instance) to the JRuby script.

Access to this Java object is intercepted by the scripting environment. Therefore it can be handled within the JRuby method implementation like an ordinary Ruby artifact. Primitive data types like Java Integer or Long are mapped into the corresponding Ruby type.

If a network event occurs, the server performs the proper callback method of the handler. This works, because the Ruby-based handler looks like a regular Java class to the server. The JRuby runtime automatically wraps the JRuby-based handler by passing it over to the Java server. Instead of getting the native JRuby handler, the Java server gets a proxy that supports all the methods of the Java interfaces that are implemented by the handler.

In conclusion

Java-based scripting runtimes strive to integrate the Java platform with the scripting language of your choice. At this early stage, actual mileage with the various scripting runtime engines will vary. In current versions of JRuby or Jython, for instance, you will find some of the newer features of the Java platform missing, such as annotations support. It is also a challenge to bridge the semantic gap between the Java language and a scripting language, sometimes requiring ugly solutions. That said, in most cases the supported features are sufficient to write enterprise-level applications using the Java platform, Java code, and the scripting language you like.

The bidirectional integrated application example in Listing 13 is a non-blocking, multithreaded SMTP server written using scripting language classes and Java classes. An existing Java network library has been used to handle low-level, performance-critical, and network-specific tasks like threading or connection management. The controlling task has been implemented using a scripting language. The emerging synergy between the Java platform and scripting languages makes it possible to write high-performance, scalable applications in a very productive and elegant way. The challenge is to choose the right language for the right task, in order to get the best of both.

On the script side you can choose between Java ports of existing scripting languages such as JRuby or Jython, and a scripting language that is designed to run on the Java platform, like Groovy. The first group adapts Java classes to look like regular scripting artifacts. Groovy uses a syntax very similar to Java code but more evolved, and each Groovy class is a full-fledged Java class. Groovy is easier for Java developers to learn than most other scripting languages and can seamlessly use the Java libraries without the need for adapters.

See the Resources section to learn more about scripting on the Java platform, polyglot programming, and the languages discussed in this article.

Gregor Roth works as a software architect at United Internet group, a leading European Internet service provider (to which among others GMX, 1&1 and Web.de belong). His areas of interest include software and system architecture, enterprise architecture management, object-oriented design, distributed computing, development methodologies, and of course Java. He began his professional career by writing C and Assembler-based microcontroller applications. In 1997 he started designing and developing large, distributed Java-based enterprise systems in the financial sector.

Learn more about this topic

1 2 3 4 5 Page 5
Page 5 of 5