Server-Side ActionScript is written entirely in Java, and one of the tremendous advantages of SSAS is that you can extend it in Java as well. If your workplace consists of ActionScript programmers who will be assembling server-side methods using SSAS, a few custom Java functions can provide any functionality that SSAS is missing. For example, the JRun 4 implementation of SSAS is missing the CF object, which is required for database queries. The functionality of the CF object can be mimicked in Java and used from within SSAS. Similarly, the file and directory manipulation techniques of ColdFusion, Java, and ASP.NET are missing from SSAS; these too can be added using Java. I'll show a few simple examples of possible extensions to SSAS and then show a simple CF object with a query( ) method that can be used from within JRun 4.
Server-Side ActionScript uses the Rhino JavaScript parser, which allows you to call Java methods as follows. To invoke a method of a Java class contained within the java package, first use the new operator to create an instance of the Java class:
var myVar = new java.packagename.classname;
Then call methods on the instance of the class as usual:
myVar.methodname(params);
You can also reference classes that are not in the java package by using the Packages prefix:
var myVar = new Packages.myPackagename.myClassname;
As an example of using a class that is not in the java package, consider the StringReverser class from Example 5-6. Simply create a new .asr file named StringReverser.asr with the method reverseString( ) in it, as shown in Example 6-5.
function reverseString (target) { var temp = new Packages.com.oreilly.frdg.StringReverser(target); return temp.getReversedString( ); }
If you use the Flash movie created in Example 5-8, JavaExample1.fla, it should give you the same results as the CFC using the same Java class.
|
More information on the Rhino parser and the techniques for accessing Java from SSAS can be found at http://www.mozilla.org/rhino/scriptjava.html.
One of the key benefits to invoking Java inside of SSAS is to take advantage of the numerous classes that are already part of the java and javax packages. The following examples show some of the simple Java classes that can be used.
When creating client/server communication, you often want to add a delay to the processing. This can be done in SSAS using some fancy scripting and the Date object, but you can do it more easily with a simple Java class, shown in Example 6-6.
function Sleep (howManySeconds) { var mySleeper = new java.lang.Thread; mySleeper.sleep(howManySeconds * 1000); }
This function allows you to pass a count, in seconds, of how long you want the script to delay. The following code will cause a three-second sleep:
Sleep(3);
Unlike ColdFusion, SSAS does not support any built-in directory access methods. Again, this can be accomplished rather easily with Java. The remote method getDirectory( ), shown in Example 6-7, returns a directory in an array, with recursive entries for subdirectories. The function can be saved in a file named Directory.asr in the webroot\com\oreilly\frdg directory.
function getDirectory (theDirectory) { // Create a file object for the root directory var myFile = new java.io.File (theDirectory) ; // Get all the files and directories under the directory var myFileList = myFile.listFiles( ); var theList = new Array( ); for (var i = 0 ; i < myFileList.length ; i ++ ) { if (myFileList[i].isDirectory( )) { // If it is a directory, create an object containing // the directory name and file list. theList.push({directory:myFileList[i].toString( ), files:getDirectory(myFileList[i])}); } else if (myFileList[i].isFile( )) { theList.push(myFileList[i].toString( )) } } return theList; }
The getDirectory( ) function accepts a directory path as an argument. The path should be in a string format, such as "e:/cfusionmx/wwwroot/com/oreilly". The path can use slashes (/) to maintain compatibility with Unix servers or backslashes (\) for Windows servers. If you use backslashes, you'll also have to escape them with another backslash, such as "e:\\cfusionmx\\wwwroot\\com\\oreilly". The function uses the File class in the java.io package. The listFiles( ) method grabs an array of files in the directory. The list elements can be files or directories. If the current item is a directory, as indicated by isDirectory( ), we create an ActionScript object with a directory property (the directory path) and a files property. The files property is an array created by calling the getDirectory( ) function recursively. This will play an important part in the process on the client once we return the result. If the current item is not a directory, we add it to the current directory's file list. Finally, we return the list to the caller.
Example 6-8 shows the client-side ActionScript for the Directory service. The method uses a Tree object from the Flash UI Components Set 2. We assume the Tree object is named directory_tree and we populate it with the contents of the remote directory.
#include "NetServices.as" // The directory to list the contents of var theDirectory = "e:\\cfusionmx\\wwwroot\\com\\oreilly"; var myURL = "http://localhost/flashservices/gateway"; if (initialized == null) { initialized = true; NetServices.setDefaultGatewayUrl(myURL); var my_conn = NetServices.createGatewayConnection( ); var myService = my_conn.getService("com.oreilly.frdg.Directory"); } // Call the remote service to retrieve a directory, given the path myService.getDirectory(new MyResponder( ), theDirectory); // Set up the Tree control named directory_tree, assumed to exist already var myRootNode = new FTreeNode(theDirectory).setIsOpen(true); directory_tree.setRootNode(myRootNode); // Responder object for the directory list, with private methods function MyResponder ( ) { this.onResult = function (myResult) { listDirectory(myResult, myRootNode); directory_tree.refresh( ); }; this.onStatus = function (myStatus) { trace("Error: "+ myStatus.description); }; function listDirectory (myArray, node) { // Populate the Tree object using the directory list from the server for (var i=0; i< myArray.length; i++) { if (myArray[i] instanceof Object) { var new_tree_node = new FTreeNode(myArray[i].directory); node.addNode(new_tree_node); listDirectory(myArray[i].files, new_tree_node); } else { node.addNode(new FTreeNode(getFile(myArray[i]), getFile(myArray[i]))); } } }; function getFile (filePath) { var lastSlash = filePath.lastIndexOf("\\"); if (lastSlash != -1) filePath = filePath.substring(lastSlash+1); return filePath; } }
Example 6-8 uses recursion again?this time on the client. The array of directories and files is returned from the server, so we cycle through the array and test each item. If it is an object, it is a directory, so we create a root node for the tree using the directory property (the path of the directory) and call the listDirectory( ) method recursively. If it is not an object, then it must be a file path, so we get the filename from the path (using another private method?getFile( )) and add a child node to the current root node of the tree. Figure 6-1 shows the Directory service in use.
This example demonstrates one of the striking things about using SSAS for remote services?the separation between client and server is almost seamless. You could, for example, add a filtering mechanism to the Directory service to filter filenames based on their file extensions. The functionality could be placed on the client or on the server. In fact, the same code would work in either place, because it is simply ActionScript code.
The preceding section showed how to use Java from within SSAS to access the filesystem. Following are some general utility functions that can be used in SSAS inside of your remote methods.
|
The moveFile( ) method shown in Example 6-9 moves a file on the server given a source file path and destination file path.
function moveFile (source, destination) { var myFile = new java.io.File(source); if (!myFile.exists( )) return false; return myFile.renameTo(new java.io.File(destination)); }
The renameFile( ) method shown in Example 6-10 renames a file on the server.
function renameFile (sourcepath, newFilename) { var myFile = new java.io.File(sourcepath); if (!myFile.exists( ) || myFile.isDirectory( )) return false; newFilename = myFile.getParent( ) + java.io.File.separator + newFilename; return myFile.renameTo(new java.io.File(newFilename)); }
The deleteFile( ) method shown in Example 6-11 deletes a file on the server. It deletes an entire directory if passed a directory path instead of a file path.
function deleteFile (filepath) { var success = false; var theFile = new java.io.File(filepath); var f; if (!theFile.exists( )) return success; if (theFile.isDirectory( )) { var allFiles = theFile.list( ); for (var i=0; i < allFiles.length; i++) { f = theFile.getAbsolutePath( ) + java.io.File.separator + allFiles[i]; deleteFile(f); } } else { try { success = theFile.delete( ); } catch(e) { // noop } } return success; }
The createDirectory( ) method shown in Example 6-12 creates a directory on the server.
function createDirectory (directoryPath) { var theDirectory = new java.io.File(directoryPath); if (theDirectory.exists( )) return true; return theDirectory.mkdir( ); }
SSAS does not contain any methods for working with SMTP servers, so there is no way to send an email from SSAS . . . or is there? Using the javax.mail.* package, you can script a method that sends emails through a SMTP server. The method shown in Example 6-13 can be saved in webroot\com\oreilly\frdg as Email.asr. It will send an email, given the recipient, sender, subject line, and message body.
function send (to, from, subject, message) { try { var mailobj = Packages.javax.mail; var props = new java.util.Properties( ); // Substitute your SMTP server address here props.put("mail.smtp.host","mail.YourServerNameHere.com"); var mySession = new mailobj.Session.getInstance(props); var myMessage = new mailobj.internet.MimeMessage(mySession); var myToField = new mailobj.internet.InternetAddress(to); var myFromField = new mailobj.internet.InternetAddress(from); var recipientType = mailobj.Message.RecipientType.TO; myMessage.setFrom(myFromField); myMessage.addRecipients(recipientType, myToField); myMessage.setSubject(subject); myMessage.setText(message); mailobj.Transport.send(myMessage); } catch (e) { throw ("Error in sending email:" + e); } return true; }
The first line of the function inside the try block sets an ActionScript variable to the javax.mail package. This technique is not immediately intuitive to the Java programmer, but it is allowed in SSAS. The Java package can be referenced with the mailobj variable thereafter.
The only change you need to make to this script is to supply a SMTP server address in place of "mail.YourServerNameHere.com". This remote method can be used with the simple email interface created in Chapter 5. Example 6-14 shows the client-side ActionScript code for generating the email application's interface. The interface elements required by Example 6-14 are shown in Table 6-4.
Interface Element |
Name |
---|---|
Input text field |
to_txt |
Input text field |
from_txt |
Input text field |
subject_txt |
Input text field |
body_txt |
PushButton |
send_pb |
MessageBox |
status_mb |
#include "NetServices.as" var my_conn; // Connection object var emailService; // Service object var myURL = "http://localhost/flashservices/gateway"; // Message box that displays status messages status_mb.visible = false; // Responder for general service methods function Responder ( ) {} Responder.prototype.onResult = function (myResults) { if (myResults == true) myResults = "Email sent!"; status_mb._visible = true; status_mb.setMessage(myResults); }; Responder.prototype.onStatus = function (theError) { status_mb._visible = true; status_mb.setMessage(theError.description); System.onStatus = this.onStatus; }; // Close the message box when OK is clicked status_mb.setCloseHandler("closeBox"); function closeBox ( ) { status_mb.visible = false; } // Initialize Flash Remoting function init ( ) { initialized = true; NetServices.setDefaultGatewayUrl(myURL); my_conn = NetServices.createGatewayConnection( ); emailService = my_conn.getService("com.oreilly.frdg.Email"); } init( ); // Send the email when the send_pb button is clicked send_pb.setClickHandler("send"); function send ( ) { var toAddress = to_txt.text; var fromAddress = from_txt.text; var subject = subject_txt.text; var body = body_txt.text; // Call the service, using the responder in the first argument emailService.send(new Responder( ), toAddress, fromAddress, subject, body); }
You can enable SSAS to retrieve email from a POP3 server using the methods of the javax.mail package. The code shown in Example 6-15 demonstrates the steps required to access a POP3 server:
Pass your authentication information to a POP3 server.
Retrieve a folder.
Parse the messages in the folder, retrieving message ID numbers, subjects, from lines, and any other information you might need.
Get the content of each email as part of the multipart email.
To do this, I've created an Inbox class that acts as a simple wrapper on the server for the POP3 access. For the sake of simplicity, the Flash interface is done in three parts:
Get the user's login information.
Grab the contents of the inbox and display the headers.
If the user clicks on an individual email, show the body.
First, the Server-Side ActionScript is shown in Example 6-15. The code is explained with inline comments.
function Inbox (myHost, myUsername, myPassword) { // The Inbox object opens the connection to the POP3 server // and provides methods to receive messages and close connections var mailobj = Packages.javax.mail; var props = new java.util.Properties( ); var mySession = new mailobj.Session.getInstance(props); this.popAccount = mySession.getStore("pop3"); this.popAccount.connect(myHost, myUsername, myPassword); this.folder = this.popAccount.getFolder("INBOX"); this.folder.open(mailobj.Folder.READ_ONLY); // The getMessages( ) method retrieves all messages this.getMessages = function ( ) { return this.folder.getMessages( ) }; // The getMessage( ) method retrieves one message given a message number this.getMessage = function(messageNumber) { return this.folder.getMessage(messageNumber); }; // The close( ) method simply closes connections to the POP3 server this.close = function ( ) { this.folder.close(false); this.popAccount.close( ); }; } // retrieveMessages( ) retrieves a list of headers given three arguments: // myHost (POP3 account), myUsername (login name), myPassword (password) function retrieveMessages (myHost, myUsername, myPassword) { var myInbox = new Inbox(myHost, myUsername, myPassword); var myMessages = myInbox.getMessages( ); // The raw headers can't be sent via Flash Remoting, // so we serialize them manually var serializedHeaders = serializeHeaders(myMessages); // Close the connection to the inbox myInbox.close( ); return serializedHeaders; } // retrieveMessage( ) retrieves one message given four arguments: // myHost (POP3 account), myUsername (login name), myPassword (password), // and the message number. function retrieveMessage (myHost, myUsername, myPassword, messageNumber) { var myInbox = new Inbox(myHost, myUsername, myPassword); var myMessage = myInbox.getMessage(messageNumber); // The raw message can't be sent via Flash Remoting, // so we serialize it manually var serializedMessage = serializeMessage(myMessage); // Close the connection to the inbox myInbox.close( ); return serializedMessage; } // serializeHeaders( ) takes a messages array and extracts/serializes // the header information (from, subject, messagenumber) function serializeHeaders (messages) { var serializedHeaders = new Array( ); var header; for (var i=0; i < messages.length; i++) { // Call our own general-purpose header serialization routine header = serializeHeader(messages[i]); serializedHeaders.push(header); } return serializedHeaders; } // serializeHeader( ) takes one message argument and extracts header information function serializeHeader (message) { var header = new Object( ); header.messageNumber = message.getMessageNumber( ); header.from = message.getFrom( ); header.subject = message.getSubject( ); return header; } // serializeMessage( ) takes a message as an argument and extracts only // the text portion of the message. The rest of the parts are simply // counted as attachments. You can enhance this function to return other // parts of messages as well. function serializeMessage (message) { var serializedMessage = serializeHeader(message); serializedMessage.attachments = 0; var tempPart; if (message.isMimeType("multipart/*")) { var content = message.getContent( ); for (var i=0; i<content.getCount( ); i++) { tempPart = content.getBodyPart(i); if (tempPart.isMimeType("text/plain")) { serializedMessage.text = tempPart.getContent( ); } else { serializedMessage.attachments++; } } } else if (message.isMimeType("text/plain")) { serializedMessage.text = message.getContent( ); } else { serializedMessage.attachments++; } return serializedMessage; }
Next, we must write the client-side ActionScript. This interface will have three "pages"?login, headers, and message. Because this is a simple demonstration, the message is retrieved from the server when the user wants to read it. In practice, you would probably set up a class to handle the messages on the client side and save the messages into a local SharedObject. The commented client-side code is shown in Example 6-16.
#include "NetServices.as" // Set up the components status_mb._visible = false; grid_lb._visible = false; message_pb.setClickHandler("messageClicked"); login_pb.setClickHandler("loginClicked"); message_pb._visible = false; // Set up global vars var login; var password; var popServer; // Set up the gateway URL and initialization function var myURL = "http://localhost/flashservices/gateway"; function init ( ) { NetServices.setDefaultGatewayUrl(myURL); var my_conn = NetServices.createGatewayConnection( ); // Set up the Email service var myService = my_conn.getService("flashremoting.com.oreilly.frdg.Email"); } init( ); // Click handler for Login button: // Get the message headers function loginClicked ( ) { status_mb._visible = true; status_mb.setButtons( ); login = login_txt.text; password = password_txt.text; popserver = popserver_txt.text; myService.retrieveMessages(new HeaderResponder( ), popserver, login, password); gotoAndPlay("login"); } // Display the headers function headersClicked ( ) { grid_lb._visible = true; gotoAndPlay("headers"); } // Get the current message function messageClicked( ) { var message = grid_lb.getSelectedItem( ).data; if (message) myService.retrieveMessage(new MessageResponder( ), popserver, login, password, message); } // Responder to grab all headers and display in list box function HeaderResponder ( ) {} HeaderResponder.prototype.onResult = function(myResults) { status_mb._visible = false; grid_lb._visible = true; for (var i in myResults) grid_lb.addItem(myResults[i].subject,myResults[i].messageNumber); message_pb._visible = true; gotoAndPlay("headers"); }; HeaderResponder.prototype.onStatus = function (theError) { trace(theError.description); }; // Responder to grab messages and display in message page function MessageResponder ( ) {} MessageResponder.prototype.onResult = function (myResults) { status_mb._visible = false; grid_dg._visible = false; gotoAndPlay("message"); subject_txt.text = myResults.subject; from_txt.text = myResults.from + " <" + myResults.address + ">"; date_txt.text = myResults.date; body_txt.text = myResults.body; }; MessageResponder.prototype.onStatus = function (theError) { trace(theError.description); }; stop( );
The client-side code uses a ListBox component rather than a DataGrid, since the ListBox component is preinstalled with Flash and freely available. You could just as easily use a DataGrid for your own implementation.
The two service calls?retrieveMessages( ) and retrieveMessage( )?each use their own responder object. When retrieving multiple messages, only the headers are retrieved, which are placed in the ListBox. When retrieving one message, the body of the message is also retrieved.
ColdFusion MX users have access to databases from SSAS using the CF.query( ) method. JRun 4 users have no way to access databases from within SSAS unless they know Java and know how to extend SSAS in Java. At the time of this writing, there is no way to return a resultset from a Java application to a Flash movie, as you would do with ColdFusion or ASP.NET. Resultsets have to be manually parsed on the server and placed into arrays or CachedResultSets. (See Chapter 7 for more details on the Java implementation of Flash Remoting.)
The code shown in Example 6-17 partially emulates the CF.query( ) method from within your JRun 4 SSAS files, but it returns the result as an array of objects that you can parse manually in the Flash movie. The CF.query( ) method for JRun uses data sources that are defined in the JRun administrative interface and takes two arguments: datasource and sql.
CF = new Object( ); CF.query = function (datasource, sql) { // InitialContext for JRun data source names var ctx = new Packages.javax.naming.InitialContext( ); // Find the data source var ds = ctx.lookup(datasource); var dbConnection = ds.getConnection( ); var stmt = dbConnection.prepareStatement(sql); if (sql.match(/^select\s*/i)) { var rs = stmt.executeQuery( ); var rsmd = rs.getMetaData( ); var myRecordSet = new Object( ); myRecordSet.columnNames = getColumnNames(rsmd); rs_hasData = rs.next( ); if (rs_hasData) { myRecordSet.items = serializeData(rs, myRecordSet.columnNames); } myRecordSet.totalCount = (rs_hasData) ? myRecordSet.items.length : 0; rs.close( ); } else { return stmt.executeUpdate( ); } stmt.close( ); dbConnection.close( ); return myRecordSet; }; // Get the column names of the resultset function getColumnNames (metadata) { var columns = new Array( ); for (var i=1; i<= metadata.getColumnCount( ); i++) columns.push(metadata.getColumnLabel(i)); return columns; } // Serialize the data for returning to Flash function serializeData (rs, columns) { var rs_hasData = true; // rows holds the rows var rows = new Array( ); // currentRow will hold individual row var currentRow = new Object( ); // z is a mapping -- integer indexes that match column names var z = new Array( ); var columnCount = columns.length; // Get index mapping of column names for (var i = 0; i < columnCount; i++) z.push(rs.findColumn(columns[i])); while (rs_hasData) { for (i = 0; i < columnCount; i++) { currentRow[columns[i]] = (rs.getObject(z[i])); } // Add to our permanent recordset rows.push(currentRow); // Clear the row out again currentRow = new Object( ); rs_hasData = rs.next( ); } return rows; }
Simply add the code shown in Example 6-17 to an SSAS file, and you will be able to access CF.query( ) functionality from a JRun 4 SSAS file. The data is returned as an array of row objects, but a little bit of client-side ActionScript code added to your Flash movie converts it into a RecordSet object:
JRunRecordset.prototype = new Recordset( ); function JRunRecordset (rs) { super (rs.columnNames); // Call the RecordSet constructor for (i in rs.items) this.addItem(rs.items[i]); // Add records to RecordSet }
Now you can convert the returned array of objects into an ActionScript RecordSet object by instantiating a new JRunRecordset object. Here is the updated responder object for the ProductsAdmin.fla file from Example 5-14 (changes are shown in bold):
SearchResult.onResult = function (result_rs) { Products_rs = new JRunRecordset(result_rs); results_txt.text = "There were " + Products_rs.getLength( )+ " records returned."; Products_rs.move("First"); getRecord( ); };
Example 5-14 also contains two RecordSet objects that feed data to the drop-down ComboBoxes. These ComboBoxes are created in the ComboBoxResponder object. Change the ComboBoxResponder.onResult( ) method from Example 5-14 to add the JRunRecordset (changes are shown in bold):
function ComboBoxResponder (cbName) {
this.cbName = cbname;
}
ComboBoxResponder.prototype.onResult = function (result_rs) {
result_rs = new JRunRecordset(result_rs);
var fields = result_rs.getColumnNames( );
var idField = '#' + fields[0] + '#';
var descField = '#' + fields[1] + '#';
DataGlue.bindFormatStrings(this.cbName, result_rs, descField,idField);
};
The custom CF.query( ) method for JRun 4 will work with most of the online examples that you'll find for Server-Side ActionScript at the Macromedia web site and other places.