Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 5
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.
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.
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.
Archived Discussions (Read only)