Advertisements
|
||
|
Recent advances in rich-client programming environments make developers more productive in part by providing languages and APIs that help reduce the amount of code needed to implement sophisticated UI features. Adobe's Flex, an open-source toolkit targeting the Flash Player, allows a developer to use MXML, a UI-specific domain language, as well as ActionScript, a derivative of JavaScript, to develop rich user interfaces.
MXML simplifies UI development by letting layout code mimic the typically hierarchical, containment-based structure of user interfaces. And ActionScript's properties and higher-order functions are helpful when working with the event-based programming model typical of rich clients. The Flex APIs leverage these language features by providing concise expressions to specify, for example, UI effects, transitions, animation, and even interaction with remote, server-hosted resources.
The examples in this article can be compiled and run either with Flex Builder 3, Adobe's Eclipse-based development environment, or with the open-source Flex SDK.
A 60-day free trial version of Flex Builder 3 can be downloaded from Adobe. If you already have Eclipse installed, you can download the Eclipse plug-in version of Flex Builder 3; otherwise, choose the full Eclipse install.
Flex is not alone in aiming to boost UI developers' effectiveness. Swing developers frequently complained that the Swing APIs, as well as the Java language, required them to code up boilerplate constructs over and over again, especially when implementing rich UI features, such as animations and advanced graphics display. To address those concerns, Sun created JavaFX, a new JVM-hosted language that helps UI developers become more effective by eliminating repetitive code that was typical of Swing applications. Similarly, Microsoft developed Silverlight with the goal of bolstering rich-client developers' productivity on the .NET runtime.
The benefit of having to write less code is hardly the sole domain of UI developers, however. Among the most important trends in recent years is the proliferation of languages hosted on the Java VM and on the .NET runtime. No longer mere curiosities, many enterprises now embrace such alternative languages as a way to improve their development teams' productivity. JRuby, Groovy, and Jython, for instance, provide dynamic alternatives to Java's static type system on the JVM. Scala, by contrast, improves on Java's type system, while providing a concise, boilerplate-free syntax for server-side development.
This article aims to illustrate that taking advantage of newer languages on both client and server have a cumulative effect on productivity when building rich-client applications that interact with server-side resources. Defining the UI in Flex allows one to use the concise syntax of MXML and ActionScript on the client. And, in this example, using Scala on the server means less boilerplate code when processing incoming requests from the client.
The article draws on the Artima tutorial on Integrating Flex with a Java EE Application, Part I, and Part II. In those articles, a Flex client remotely invokes methods on sever-side Java objects. Data between Flex and Java are marshalled via the Action Message Format (AMF) binary protocol, and the open-source BlazeDS middleware helps expose Java objects' methods for remote invocation from the Flex client.
This article further develops that example by replacing Java with Scala on the server. AMF and BlazeDS play similar roles as before. In addition to switching to Scala from Java, the server-side code will use the Java Persistence API (JPA) from Scala to provide database access.
You don't need to be familiar with Scala to follow these examples. Although complete, the examples in this article are meant to show that, if you are open to using newer JVM languages, such as Scala, very little code is needed to implement an end-to-end rich-client application hosted in a Java EE environment.
Scala is a relatively new language for the JVM. It supports a static type system, and sports many advanced features, such as type inference, higher-order functions, and properties. Scala has a rich API, but it is also compatible with existing Java classes: Any Java API can be used from Scala code.
ActionScript, the main Flex programming language, shares many similarities with Scala. For example, on a syntactic level, variables and method types in both languages are indicated with a :
character after the variable or method name, followed by the type. The following is both valid ActionScript and valid Scala code:
var a: String = "hello, there" // Valid Scala and ActionScript
Note that semicolons after a statement are inferred by the compiler in both languages.
Methods are indicated by function
in ActionScript, and by def
in Scala:
class AnActionScriptClass { // ActionScript public function anActionScriptMethod(): String { return "hello, there" } }
Note that Scala methods don't require a return
statement:
class AScalaClass { // Scala def aScalaMethod(): String = { "hello, there" } }
More important than these syntactic similarities, both languages support the ability to pass functions as values, or higher-order functions. A common ActionScript idiom is to write a function as a handler for an event, and then simply assign that function as a property value to an object, often via MXML, Flex's UI layout language:
private function buttonHandler(event:MouseEvent): void { // ActionScript trace("Someone clicked me") } ... <mx:Button label="Click me" click="buttonHandler"/> // MXML
You can also pass a function as a parameter to another function in ActionScript:
private function doSomething(): String { // ActionScript return "I'm doing something" } private function doItNow(f: Function): void { var result: String = String(f()) trace(result) }
You can achieve similar functionality in Scala as well:
def doSomething() = "I'm doing something" // Scala def doItNow(block: => Any) = { val result = block println(result) }
The block: => Any
notation in Scala specifies block
as a by-name parameter, or code that will be executed later, inside the doItNow()
method. Any
sits at the top of Scala's type hierarchy, indicating that the block can result in any type.
In addition to similarities in syntax and concepts, developing with Scala and Flex is also made easier by the fact that Adobe chose Eclipse as the platform for its FlexBuilder IDE. FlexBuilder can be downloaded both as a standalone product, as well as an Eclipse plug-in.
Because Eclipse also supports Scala development, it is possible to use the same tool for developing an application in which the client uses Flex and the server uses Scala. The concluding section of this article includes detailed instructions on setting up Eclipse to effectively work with Scala and Flex.
The tutorial on Integrating Flex with a Java EE Application relied on the open-source BlazeDS tool to make server-side Java objects available for remote method calls from Flex. One of the most productive features of working with BlazeDS is that changing the implementation language on the server has very little impact on client-side code. That's because BlazeDS requires only that the server provide Java objects at the binary level—BlazeDS does not care what language you use to define those objects.
The example book inventory application consists of two domain objects: Book
describes a book title, such as For Whom the Bell Tolls or Programming in Scala; Publisher
, in turn, describes a book's publisher.
Since the domain model stays the same regardless of server-side implementation language, we can re-use the ActionScript classes from the earlier implementation of the book inventory application. The only difference is that we add an id
property to each class; that property is used by the Java Persistence API on the server to maintain object identity. Note the RemoteClass
annotation on each class: The alias
parameter for that annotation refers to the fully qualified name of the server-side class:
package scalaflex { [RemoteClass(alias="scalaflex.Publisher")] public class Publisher { public var id:Number; public var name:String; } } package scalaflex { [RemoteClass(alias="scalaflex.Book")] class Book { public var id:Number; public var title:String; public var authors:String; public var year:Int; public var price:Number; public var stock:Int; public var publisher:Publisher; } }
To obtain the current inventory listing from the server, we need to arrange for the Flex client to invoke the getCurrentInventory()
method on the remote InventoryManager
object on the server. Just as the domain classes, the ActionScript code to perform the remote call is also agnostic to the server-side implementation language: We define on the client a RemoteObject
destination, set listeners on that remote destination, and then invoke the getCurrentInventory()
method:
[Bindable] private var books:ArrayCollection; private function onCreationComplete():void { var inventoryManager:RemoteObject = new RemoteObject(); inventoryManager.destination = "inventorymanager"; // Remote destination name inventoryManager.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void { books = event.result as ArrayCollection }); inventoryManager.addEventListener(FaultEvent.FAULT, ... inventoryManager.getCurrentInventory(); }
When the remote call returns, Flash Player on the client deserializes the return value of the method into an ActionScript ArrayCollection
. Since the domain classes are annotated with the remote class' names, Flash Player ensures that each element of that collection is an instance of the ActionScript Book
class defined above and, further, that each book's publisher
field refers to an instance of a Publisher
object. This demonstrates one of BlazeDS's biggest productivity boosts: It automates serialization to and from server-side objects and their client-side equivalents.
In order to enable the service for remote method invocation, BlazeDS requires that a Java class be available to BlazeDS's class loader with the method names and parameters specified by the client. That class must be registered in BlazeDS's configuration, and associated with a remote destination name. The following snippet accomplishes this registration in BlazeDS's remoting-config.xml
file:
<destination id="inventorymanager"> <properties> <source>scalaflex.InventoryManager</source> <scope>application</scope> </properties> </destination>
Based on this configuration, BlazeDS creates an instance of scalaflex.InventoryManager
, and associates that instance with the inventorymanager
remote destination name. scalaflex.InventoryManager
can be implemented in any JVM-based programming language, as long as BlazeDS's class loader can locate that class and instantiate it via a no-arg constructor.
The following code example illustrates a possible Scala implementation of InventoryManager
, using the Java Persistence API to obtain results from a database. The getCurrentInventory()
method performs a Java Persistence API query, and returns a list of Book
s whose inventory count is greater than 0.
package scalaflex import InTransaction._ class InventoryManager { def getCurrentInventory() = { txn[java.util.List[Book]] { val q = EmThreadLocal.get().createQuery("from Book as bk where bk.stock > 0") q.getResultList.asInstanceOf[java.util.List[Book]] } } }
InventoryManager
in Scala, using a Java Persistence API query
The first thing to note about this example is that the method's return type, java.util.List[Book]
, is inferred by the Scala compiler, and thus can be left out of the source code.
You may also notice the txn {...}
construct that surrounds the JPA query. Although txn {...}
looks like a control structure in the language, similar to if
or while
, it is actually a method defined in another class, InTransaction
. In Scala, when a method has only one argument, you can use curly braces instead of parenthesis to surround that argument. In this case, txn
takes a block of code, ensuring that that code is executed in the context of a JPA transaction.
Members of InTransaction
are imported at the top of the source file: the _
is a Scala wildcard character, meaning "import all visible members of InTransaction
." The following shows the implementation of the txn
method:
package scalaflex object InTransaction { def txn[T](block: => T): T = { val entityMan = getEntityManager() // Obtain the JPA Entity Manager // The code is not shown. EmThreadLocal.set(entityMan) // Set the entity manager in // a threadlocal variable so that // it is available to the code // performed under the transaction. val txn = entityMan.getTransaction() try { txn.begin() val res = block // Execute the block passed in as txn.commit() // a parameter. res // Return the results } finally { if (entityMan.getTransaction().isActive()) entityMan.getTransaction().rollback() if (entityMan.isOpen()) entityMan.close() EmThreadLocal.remove() // Remove the entity manager from // the threadlocal. } } }
Note that the block passed to the txn
method may or may not return some results. In the case of getCurrentInventory()
it returns a list of Book
s. When invoking txn
, we specify the type of return value we're expecting from the method:
txn[java.util.List[Book]] { ... }
This may seem like an unnecessarily verbose construct but, in fact, specifying this value is an important step in making the application reliable. Note that in Listing 2, the Flex client casts the result from the remote method call to an ArrayCollection
consisting of ActionScript Book
instances:
private var books:ArrayCollection; ... function(event:ResultEvent):void { books = event.result as ArrayCollection }
By explicitly specifying the result type of the code performed under the JPA transaction on the server, we can be confident that the client can always perform this cast, and that the books
collection will always consist of Book
objects.
There are two requirements on the entity classes on the server: First, BlazeDS must be able to properly serialize instances of such classes in a way that the Flash Player can then deserialize that data into typed ActionScript classes. Second, the entity classes must be written such that they can be persisted via the Java Persistence API.
Both requirements can be satisfied by making the entity classes follow the JavaBeans pattern of getter and setter methods, and by providing a no-arg constructor. Unlike Java, but similarly to ActionScript, Scala supports properties, and thus getters and setters would not be needed, if not for BlazeDS's requirement. Tailor-made for just such a requirement, Scala's BeanProperty
annotation instructs the Scala compiler to generate getter and setter methods for class fields:
package scalaflex import scala.reflect.BeanProperty import java.math.BigDecimal import javax.persistence.{Id, GeneratedValue, ManyToOne, Entity} @Entity case class Publisher( @Id @GeneratedValue @BeanProperty var id: Long, @BeanProperty var name: String) { def this() = (0, null) } @Entity case class Book( @Id @GeneratedValue @BeanProperty var id: Long, @BeanProperty var authors: String, @BeanProperty var year: Int, @BeanProperty var price: BigDecimal, @BeanProperty var stock: Int, @ManyToOne @BeanProperty var publisher: Publisher) { def this() = (0, null, 0, null, 0, null) }
In Scala, a class's properties can be specified as part of the class declaration. By defining the bean properties properties as var
s, the compiler generates both getter and setter methods. Note also that JPA annotations can be used on Scala entity classes just the way you'd use them on Java classes (see the note at the end of the article for an important limitation).
Finally, note the class declarations are prefaced with the case
keyword. Scala's case classes are another example of the Scala compiler generating meaningful boilerplate code. For a case class, the compiler creates a factory method, such as Publisher(id: Long, name: String)
. It also ensures that the fields specified in the class declaration are accessible in the class; finally, the compiler also provides sensible implementations of toString()
, equals()
, and hashCode()
.
The final requirement for this example to work is a persistence.xml
file. Mandated by the JPA specification, the file contains configuration information needed to create the persistence context:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence \ http://java .sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="MileageCommander" transaction-type="RESOURCE_LOCAL"> <non-jta-data-source>java:comp/env/scalaflex</non-jta-data-source> <class>scalaflex.Publisher</class> <class>scalaflex.Book</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence>
persistence.xml
configuration file
Listing 7 specifies a non-JTA data source called scalaflex
, as well as the persistent entity classes. Finally, property values are provided for the Hibernate JPA entity manager implementation. The non-JTA data source is suitable for use in a standalone application, or inside Tomcat.
As in the Java version of this application, you will need to ensure that the BlazeDS configuration files are available to the Web server. In addition to the persistence provider JAR files, you will also need to place the Scala JAR file, scala-library.jar
, to the Web application's lib
directory. The outline of your Web application's directory structure may look as follows:
ScalaFlexWebApp |-- HTML and JSP files |-- Compiled SWF files |-- WEB-INF |-- web.xml |-- lib |-- scala-library.jar, hibernate-entitymanager.jar, ... |-- flex |-- services-config.xml |-- remoting-config.xml
In addition to the configuration files in the the flex
directory, you also need to reference BlazeDS's messaging servlet in web.xml
:
<servlet> <servlet-name>MessageBrokerServlet</servlet-name> <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class> <init-param> <param-name>services.configuration.file</param-name> <param-value>/WEB-INF/flex/services-config.xml</param-value> </init-param> <init-param> <param-name>flex.write.path</param-name> <param-value>/WEB-INF/flex</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
Finally, for completeness' sake, if you're using Tomcat to run this example, you can define the scalaflex
resource referenced in persistence.xml
inside Tomcat's TOMCAT_HOME/conf/context.xml
file. This resource provides information to the JPA implementation to connect to a database. Other Web servers or application servers will likely use different means of specifying this sort of data:
<Context> <Resource name="scalaflex" type="javax.sql.DataSource" auth="Container" driverClassName="org.postgresql.Driver" username="staircase" password="lambda" url="jdbc:postgresql://localhost/scalaflexdb"/> </Context>
This article demonstrated that relatively sophisticated functionality can be provided by using Flex on the client, Scala on the server, and BlazeDS as an intermediary to facilitate remote calls and object serialization.
The following steps describe one way of working with Scala and Flex from the same Eclipse IDE. Note that Adobe's Flex Builder is a mature, commercial Eclipse plug-in, and you will likely encounter very few, if any, issues in getting it to install and work in any recent Eclipse version. The Scala IDE for Flex is also rapidly becoming more mature, but you may need to follow these steps to ensure you can effectively work with the current version:
services-config.xml
file described in the BlazeDS documentation. This step accomplishes two things: First, it will instruct Flex Builder to add the path to services-config.xml
to the Flex compiler as the services
parameter; and it will tell Flex Builder to output its compiled files into your Web application's output folder.In general, any Java annotation can be used with Scala classes. Such annotations are left untouched by the Scala compiler and are compiled into the binary Java classes, if the annotations have the appropriate retention.
In the current, 2.7.4. version of Scala, however, there are some limitations to the kinds annotations you can use. There are specifically two problem areas: First, annotations with variable optional arguments may not be processed properly by the Scala compiler. javax.persistence.OneToMany
is an example of such an annotation, with 4 optional arguments. Second, nested annotations may also not be processed correctly, and result in compiler errors.
The upcoming, 2.8 version, of Scala will fix those issues. In the meanwhile, if you need to use such annotations in a JPA application, you can opt instead for specifying entity relationships inside an orm.xml
file. In any case, although the JPA spec focuses a great deal on annotations, you may find that specifying persistence-related information in a separate file leaves your source code cleaner.
Flex Builder 3
http://www.adobe.com/products/flex/features/flex_builder/
Scala Language Web site
http://www.scala-lang.org
BlazeDS
http://opensource.adobe.com/wiki/display/blazeds/BlazeDS
Adobe's Flex framework
http://www.adobe.com/products/flex/
Adobe's AIR SDK
http://www.adobe.com/products/air/tools/sdk
Flex.org
http://www.flex.org
Action Message Format (AMF) 3. Wikipedia. Accessed on April 22, 2009.
http://en.wikipedia.org/wiki/Action_Message_Format
Steve Yegge's perspective on why less code is better:
http://steve-yegge.blogspot.com/2007/12/codes-worst-enemy.html
The definitive tutorial and reference for Scala is Programming in Scala, by Martin Odersky, Lex Spoon, and Bill Venners: http://www.artima.com/shop/programming_in _scala |
Have an opinion? Readers have already posted 1 comment about this article. Why not add yours?
Frank Sommers is an editor with Artima Developer. He is also founder and president of Autospaces, Inc., a company providing collaboration and workflow tools in the financial services industry.
Frank Sommers is Editor-in-Chief of Artima.
James Ward is Adobe's Flex evangelist. He is also author, with Bruce Eckel, of First Steps in Flex, a step-by-step introduction to Flex development. |
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.