5.3 Service Name Mappings

The different types of services that ColdFusion supports are referenced in different ways from the client when calling NetConnection.getService( ). For example, you must reference a service implemented as a ColdFusion page differently than a ColdFusion Component. Table 5-2 shows how different types of services should be referenced using getService( ).

Table 5-2. How to reference remote services in ColdFusion

Service type

Name of service

Name of remote function

ColdFusion page

The directory that the ColdFusion page (.cfm file) resides in, expressed in dot notation

The name of the ColdFusion page that you want to invoke in the specified directory

ColdFusion Component

The entire path to the ColdFusion Component (.cfc component file) from the web root, expressed in dot notation

The name of the function in the specified .cfc file

Server-Side ActionScript

The full path to the SSAS file (.asr file) from the web root, expressed in dot notation

The name of the function in the specified .asr file

5.3.1 Order of Service Lookup

In order to adapt to a variety of software architectures and personal preferences, ColdFusion supports Flash Remoting services implemented in a few different ways. You can write your remote services as ColdFusion pages, ColdFusion Components, or Server-Side ActionScript. When the Flash client references a remote service on the ColdFusion Server, ColdFusion looks up the service on the server and invokes it, returning the result to the Flash client. Flash Remoting looks for services in this order:

  1. ColdFusion page (.cfm or .cfml)

  2. ColdFusion Component (.cfc)

  3. Server-Side ActionScript (.asr)

ColdFusion Components can also be used to create web services that can be accessed with Flash Remoting, as covered in Chapter 10.

Now that we have covered some of the fundamentals of Flash Remoting with ColdFusion, let's look at some more specific elements of Flash and ColdFusion integration.

5.3.2 Invoking a ColdFusion Page Service from Flash

To invoke a ColdFusion page from a Flash application, follow these steps:

  1. Set the gateway URL.

  2. Create a connection object using NetServices.createGatewayConnection( ).

  3. Create a service object by invoking getService( ) on the connection object obtained in Step 2. The service path includes the directory name, but not the .cfm page name.

  4. Invoke the page as a method of the service object obtained in Step 3. (That is, use the page name, without the .cfm extension, as the method name.)

The first two steps should be very familiar by now:

NetServices.setDefaultGatewayUrl("http://localhost/flashservices/gateway");
var myConnection_conn = NetServices.createGatewayConnection( );

To create the service object in Step 3, specify the service name in the call to getService( ). The service name is the name of the directory containing the .cfm file (relative to the web root), substituting dots for slashes, but it does not include the .cfm file's name. For example, to invoke a .cfm page called sendEmail.cfm located in the directory wwwroot/com/oreilly/frdg/Email, create a reference to the service, as follows:

var emailService = myConnection_conn.getService("com.oreilly.frdg.Email", this);

Use the .cfm page name (without the extension) as the remote function name. The following code invokes the sendEmail.cfm ColdFusion page:

emailService.sendEmail( );

The name of the ColdFusion page is not case-sensitive on a Windows server, so emailService.sendEmail( ) works as well as emailService.SendEmail( ). However, it is good practice to use the correct case in your code for compatibility with Unix servers.

You can send as many arguments to the sendEmail.cfm page as you want:

emailService.sendEmail(toAddress, fromAddress, subject, body);
5.3.2.1 Using the Flash variable scope to pass data between Flash and a ColdFusion page

To access the arguments passed from Flash, use the Flash.Params variable within your ColdFusion page. Flash.Params is an array containing sequentially numbered elements, one for each argument passed in from the Flash application.

Unlike ActionScript arrays, which start from 0, ColdFusion arrays start from 1.

For example, the following ColdFusion code accesses the four variables passed into the sendEmail.cfm page from Flash:

<cfmail to="#Flash.Params[1]#" from="#Flash.Params[2]#" 
 subject="#Flash.Params[3]#">
 #Flash.Params[4]#
</cfmail>

In ColdFusion, you can use variables as tag attributes without using pound signs (#). If the variables are not being used as attributes, or if you use quotes around the variables, you have to use pound signs.

Therefore, the preceding example could be written as:

<cfmail to=Flash.Params[1] from=Flash.Params[2] subject=Flash.Params[3]>
 #Flash.Params[4]#
</cfmail>

5.3.2.2 Using named parameters to pass data from a Flash application to a ColdFusion page

Instead of passing ordered arguments to a ColdFusion page, you can also attach properties to an object and pass that object to your remote function. Any properties attached to the object become named arguments to the function. This example passes the to, from, subject, and body arguments as named arguments:

var args = new Object( );
args.to = toAddress;
args.from = fromAddress;
args.subject = subject;
args.body = body;
emailService.sendEmail(args);

You can express the same thing in a more succinct manner using an object literal:

emailService.sendEmail(
  {to:toAddress, from:fromAddress, subject:subject, body:body});

To access named arguments on the server, treat the Flash variable as though it were a structure:

<cfmail to=Flash.to 
 from=Flash.from
 subject=Flash.subject>
#Flash.body#
</cfmail>

You can also use the attribute name inside of single quotes within brackets. Don't forget to use pound signs and double quotes to surround each element, as follows:

<cfmail to="#Flash['to']#" 
 from="#Flash['from']#"
 subject="#Flash['subject']#">
#Flash['body']#
</cfmail>

Because named arguments are accessed by property name and not by order, the preceding ColdFusion examples work even if the arguments are attached to the object in a different order, such as:

emailService.sendEmail(
  {subject:subject, from:fromAddress, to:toAddress, body:body});
5.3.2.3 Returning data to Flash from a ColdFusion page

Returning data from a ColdFusion page to Flash is as simple as assigning the return value to the Flash.Result variable. For example, to return the string "Email sent!" to the Flash client making the remote call, use the following code:

<cfset Flash.Result="Email sent!" />

As soon as the variable is defined, the data is returned to the client.

Remember, you can also return complex objects like Arrays, Structs, Dates, Queries and XML objects to the Flash client.

Example 5-1 shows the complete CF code, including some basic error handling and sending a return value. You can save the file in the remote services directory webroot\com\oreilly\frdg\cfpages under the name sendEmail.cfm.

Example 5-1. ColdFusion code for the remote service sendEmail.cfm
<cftry>
  <cfmail to = Flash.to 
   from = Flash.from
   subject = Flash.subject>
#flash.body#
  </cfmail>
  <cfcatch type="Any">
    <cfthrow message = "There was an error">
  </cfcatch>
</cftry>
<cfset Flash.Result = "Email sent">

The corresponding client-side ActionScript code is shown in Example 5-2. It assumes that a MessageBox component (from the Macromedia UI Components Set 2) named status_mb is available to display messages upon a successful send or an error. It assumes that the movie has text fields named to_txt, from_txt, subject_txt, and body_txt and containing appropriate text. The final sendEmail.fla file can be downloaded from the online Code Depot.

Example 5-2. ActionScript code for sendEmail.fla
#include "NetServices.as"

var my_conn;         // Connection object
var emailService;    // Service object 
var myURL = "http://localhost/flashservices/gateway";
// Responder for general service methods
function Responder ( ) {
  this.onResult = function (myResults) {
    if (myResults == null) 
      myResults = "Email sent!";
    status_mb._visible = true;
    status_mb.setMessage(myResults);
  };

  this.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.cfpages");
}

init( );

// Send the email when the send_pb button is clicked
send_pb.setClickHandler("send");

function send ( ) {
  var args     = new Object( );
  args.to      = to_txt.text;
  args.from    = from_txt.text;
  args.subject = subject_txt.text;
  args.body    = body_txt.text;
  // Call the service, passing the responder and then the arguments
  emailService.sendEmail(new Responder( ), args);
}

5.3.3 Flash Remoting Services as ColdFusion Components

Writing Flash Remoting services as ColdFusion pages is relatively quick and easy. However, ColdFusion pages primarily are designed to return dynamic HTML to web browsers, as opposed to providing generic services to a variety of clients. ColdFusion Components (CFCs), on the other hand, are specifically designed to provide services to various clients.

5.3.3.1 The theory behind ColdFusion Components

CFCs are loosely modeled after Java objects and should be designed and written with many of the same principles in mind. The objective of a CFC should be to provide well-encapsulated functionality to a variety of clients. Encapsulation refers to a service's ability to provide functionality to a client without exposing anything about the implementation behind the functionality.

For example, consider a CFC called UserServices.cfc, containing a function called createUser( ), which takes several arguments pertaining to typical user data and returns a numeric key that is associated with the new user. To clients invoking createUser( ), it is not apparent whether the user information is written to a database or saved in a text file. In theory, the client shouldn't care even if the implementation behind createUser( ) changes completely, as long is it continues to take the same arguments and return a numeric key.

Now, consider an implementation of createUser( ) that requires database connection parameters to be passed to the method in addition to user information. If the implementation of createUser( ) changes to use text files, clients must change their code to pass in a file path rather than database connection parameters. It's easy to see how small changes can require changes elsewhere in an application, requiring rewriting and retesting the code.

CFCs can be invoked from Flash, ColdFusion pages, and even other CFCs. Since CFCs are typically client-agnostic (they don't care what type of client invokes them), it is important that their implementations be kept free of client-specific code. For example, if you were to access arguments passed into the CFC through the Flash variable scope, your CFC couldn't be called successfully from a ColdFusion page or another CFC. Conversely, if your CFC used the <cfoutput> tag to return HTML, it would no longer be usable from Flash. For that reason, it is also advisable not to use constructs such as session or application variables in your CFCs, as that breaks the encapsulation of the component functionality.

The other primary goal of a CFC should be code re-use. Whenever you find yourself implementing the same logic in more than one place in your code, you should consider abstracting the logic into a CFC. Once your CFC properly encapsulates your logic, you can reuse it from anywhere, including different applications and different types of clients.

Additionally, CFCs support inheritance, which means you can "layer" your CFC to get the most functionality out of the fewest lines of code. Multiple layers of abstraction allow you to maintain code in a single location, and they also allow you to change the implementation behind certain services without having to rewrite every client that depends on that CFC.

For example, let's say you create a component method called getUsStates( ) that returns a Query object with the names and abbreviations of all the U.S. states. The logic is generic enough that it could be used by multiple clients or applications without concern for whether the list of states is hardcoded or stored in a text file or database. As long as the CFC's interface never changes, none of the applications or clients calling getUsStates( ) need to change. Let's explore how ColdFusion provides everything you need to keep your CFC generic and well-encapsulated.

5.3.3.2 The structure of ColdFusion Components

As you can see from the following general structure of a CFC, the component is wrapped in a single <cfcomponent> tag containing any number of nested <cffunction> tags or inline ColdFusion code:

<cfcomponent extends="superComponent" displayName="displayName">
  <cffunction name="functionName"
   returnType="dataTypeToReturn"
   roles="securityRole"
   access="clientAccess"
   output="trueOrFalse"
   hint="functionHint "
   description="functionDescription">     
    <cfargument name="variableName"
     type="argumentDataType"
     required="trueOrFalse"
     default="defaultValue"
     description="argumentDescription"/>
    <!--- Component implementation here --->
    <cfreturn dataToReturn />
  </cffunction> 
  <cffunction name="anotherFunction">
    <!---body of function --->
  </cffunction>
</cfcomponent>

Any code that is not contained in a function is executed whenever the component is called. Your CFC needs to be stored in a file with a .cfc extension, which must reside in or below your web root.

5.3.3.3 The <cfcomponent> tag

Table 5-3 describes the possible attributes of a <cfcomponent> tag.

Table 5-3. Attributes of the ColdFusion <cfcomponent> tag

Attribute name

Type

Required

Description

Possible values

extends

string

No

Indicates another CFC that the current component inherits functionality from

Any local CFC name

displayName

string

No

Descriptive name used for self-documentation

An arbitrary description

hint

string

No

Display hint for Dreamweaver MX, CFC Explorer, or other introspection mechanism

Any descriptive hint

output

string

No

Specifies whether to suppress output from the component

"yes" or "no"

When a component extends another component, it inherits the properties and functions from the component it is extending. When structuring your CFC, remember that any functions or properties a component inherits from its parent must be just as valid for the inheriting component as it is for the parent. Any component that extends another component should be a subclass, or a more specific version, of the component it is extending.

For example, let's say you've written an ecosystem simulation using a component called PrayingMantis.cfc with the functions getLegCount( ) and getPrimaryDiet( ). You decide that your simulation needs a grasshopper, so you create Grasshopper.cfc and add getLegCount( ) and getPrimaryDiet( ) functions to it. Although praying mantises and grasshoppers have different diets, they both have six legs. Rather than duplicating getLegCount( ) in two places, you create a third component called Insect.cfc that contains the function getLegCount( ). Any function or property that is common to all insects goes in Insect.cfc, while functionality specific to one type of insects should go in a subclass of Insect.cfc. Even if you have 20 different types of insects inheriting from Insect.cfc, you need to write the code only once. And if entomologists discover they have been miscounting all these years and insects actually have seven legs, you only have to change the getLegCount( ) function in a single location to automatically change your entire insect collection.

5.3.3.4 The <cffunction> tag

Table 5-4 describes the possible attributes of a <cffunction> tag.

Table 5-4. Attributes of the <cffunction> tag

Attribute name

Type

Required

Description

Possible values

name

string

Yes

The name of function, which is the name by which it will be invoked

Any valid string

returnType[1]

string

No

The datatype of the value returned by the component

"any", "array", "binary", "boolean", "component name", "date", "guid", "numeric", "query", "string", "struct", "uuid", "variable name", "void", "xml"

access

string

No

Indicates what type of clients have access to the function

"public", "private", "package", or "remote"

roles

string

No

Indicates security roles for this function

Any valid user role

output

string

No

Indicates whether output from the component is written back to the client

"yes" or "no"

hint

string

No

Usage hint for self-documentation

Any string hint

description

string

No

Displayed when introspecting components, such as while browsing remote services from the Flash authoring environment

Any string description

[1] Use "void" when there is no return value and use "any" when returning an object of type ASObject.

The access attribute of the <cffunction> tag determines what type of clients can access your component functions. It has four possible values:

"public"

Function is available to all local pages or other CFCs

"private"

Function is available only to other functions in the same CFC

"package"

Function is available only to CFCs in the same directory, including the CFC in which the function is declared

"remote"

Function can be invoked remotely (access must be "remote" to work with Flash Remoting)

Roles are discussed in detail later in this chapter in the section Section 5.6.3.

5.3.3.5 The <cfargument> tag

Table 5-5 describes the possible attributes of a <cfargument> tag.

Table 5-5. Attributes of the <cfargument> tag

Attribute name

Type

Required

Description

Possible values

name

string

Yes

The argument's variable name

Any valid variable name

type[2]

string

No

The argument's datatype

"any", "array", "binary", "boolean", "component name", "date", "guid", "numeric", "query", "string", "struct", "uuid", "variable name", "xml"

required

Boolean

No

Whether the argument is required

"yes" or "no" (defaults to "no")

default

string

No

A default value if argument it is not defined

Any value valid for the argument's datatype

description

string

No

Displayed when browsing remote services from the Flash authoring environment

Any string description

[2] Use "any" when passing an object of type ASObject to the function.

An argument whose required attribute is "yes" must be passed in at the time the function is invoked (unless the default attribute is also specified); otherwise, the function invocation fails. In the case of Flash Remoting, the responder object's onStatus( ) method is called with an error object indicating which arguments were missing.

5.3.3.6 Organizing ColdFusion Components using packages

The directory structure you use to organize your CFCs is called a package structure. Packages are useful for grouping files in logical ways. For example, I might put the PrayingMantis.cfc and Grasshopper.cfc files discussed earlier in the directory webroot\com\oreilly\frdg\bugs. All ColdFusion Components in the bugs directory in the frdg project should relate to little critters with several legs, whether they are insects or arachnids. The actual package name is relative to the document root and uses dots rather than slashes; so, in the previous example, the package name would be "com.oreilly.frdg.bugs". As discussed in Chapter 2, using domain names as directory structures prevents namespace collisions and keeps your code better organized.

5.3.3.7 Invoking ColdFusion Components From Flash

Invoking a CFC from Flash is very similar to invoking a ColdFusion page. To create an instance of a service in your Flash movie, you call getService( ) on your NetConnection instance, passing in the fully qualified component name (the entire name of the CFC, including the package name). Remember that package names are relative to the document root and use dots in place of slashes.

To invoke a CFC from a Flash application, follow these steps:

  1. Set the gateway URL.

  2. Create a connection object using NetServices.createGatewayConnection( ).

  3. Create a service object by invoking getService( ) on the connection object obtained in Step 2. The service path includes the directory name and the name of the .cfc file, excluding the .cfc extension.

  4. Invoke a function within the component as a method of the service object obtained in Step 3. Any functions defined within the component can be accessed as methods of the service object.

The following code creates an instance of the service, which points to our Grasshopper component and invokes the getLegCount( ) method:

var myURL = "http://localhost/flashservices/gateway";
var myService = "com.oreilly.frdg.bugs.Grasshopper";
NetServices.setDefaultGatewayUrl(myURL);
var my_conn = NetServices.createGatewayConnection( );
var grasshopperSevice = my_conn.getService(myService, responderObject);
grasshopperService.getLegCount( );

As with the earlier ColdFusion page example, we pass the service name to getService( ). However, note that when using a CFC the service path includes the name of the .cfc file (in this case Grasshopper.cfc) without the .cfc extension. Contrast this with the case of a ColdFusion page, in which the .cfm file is not part of the service path passed to getService( ) but is instead invoked as the method on the service object returned by getService( ).

Remember that a component can define multiple functions (one for each <cffunction> tag), and each one can be accessed as a method of the service object returned by getService( ). That is, as long as the responder object passed to getService( ) is capable of handling different types of results, you can reuse the same service instance to call other functions on the same component:

grasshopperService.getPrimaryDiet( );

You can pass arguments into a remote CFC function the same way you pass arguments to the ColdFusion page. For example, if the component defines a setSpecies( ) function, you can pass arguments to it as follows:

grasshopperService.setSpecies("Melanoplus Differentialis");

5.3.4 Examples of Flash Remoting Using ColdFusion Components

ColdFusion Components are extremely versatile, because the code inside of the <cffunction> tags can be written in CFML or CFScript and can be included from external files. Including code in your functions using the <cfinclude> tag is a good way to reuse code, and it allows you to add layers of abstraction to your application. CFC functions can also invoke each other (as long as the access attributes allow for it). They can even create instances of Java objects and use them.

The next three subsections demonstrate various techniques. The first section is a working example of a CFC that performs a service (sends an email) and doesn't return anything to the client. The next section demonstrates performing a database query and returning data to the client. It also shows the benefit of component functions calling other component functions. The last subsection shows how to wrap a Java class in a CFC so it is more easily accessible to Flash through Flash Remoting.

5.3.4.1 Using a ColdFusion Component to send email

Sending email is a common task that nearly all web-based applications support. There is no way to send email directly from a Flash movie unless you use a mailto URL in conjunction with the getURL( ) function, which is far from ideal since it assumes the user is on his own computer and has a mail client and server properly configured. These are not necessarily things you can count on, since Flash runs on many different types of devices and in many different places. The solution is to use Flash Remoting to delegate the task to a server. Using ColdFusion, the entire procedure can be done in just a few lines of code.

Example 5-3 shows code for a CFC capable of sending email on behalf of a client. It is analogous to the ColdFusion page of Example 5-1 but is written as a component. Name the component Email.cfc and put it in the package com.oreilly.frdg.

Example 5-3. Sending an email with a CFC using Email.cfc
<cfcomponent>
  <cffunction name="sendEmail" access="remote">
    <cfargument name="to" required="true" />
    <cfargument name="from" required="true" />
    <cfargument name="subject" required="true" />
    <cfargument name="body" required="true" />
    <cftry>
      <cfmail to = to
       from = from
       subject = subject>
#body#
      </cfmail>
      <cfcatch type="Any">
        <cfthrow message = "There was an error">
      </cfcatch>
    </cftry>
  </cffunction>
</cfcomponent>

In order for Email.cfc to work, you must have an email server configured through the ColdFusion administrator.

The component in Example 5-3 is a simple, generic service that can be invoked remotely from a Flash application (since the access attribute is "remote") or locally from a ColdFusion page or even another CFC. The ActionScript code from Example 5-2, used with the ColdFusion page, must be modified slightly to invoke the sendEmail( ) method from the component. On the last line of the init( ) function in Example 5-2, change the getService( ) invocation to use the path to the new component (omit the .cfc extension):

emailService = my_conn.getService("com.oreilly.frdg.Email");

Note that we've named our function sendEmail( ) in imitation of the ColdFusion page named sendEmail.cfm from Example 5-1. This allows us to invoke sendEmail( ) using the same client-side code from the original Example 5-2; however, in the earlier case sendMail was the name a .cfm page, and here it is the name of a function within a CFC:

emailService.sendEmail(new Responder( ), args);

Even though the sendEmail( ) method doesn't return anything to the client, the responder object's onResult( ) method is called when the function completes. No argument is passed back to the onResult( ) function. Notice that the ActionScript code in Example 5-2 created a default message to be displayed if there was no result from the server.

5.3.4.2 Returning a Query object

Let's see how to return a value from a CFC. The CFC listed in Example 5-4 defines two functions, which become methods of the component. The getAllStates( ) method performs a database query to retrieve information on all the U.S. states and returns the result. Notice how the getStatesByRegion( ) method first calls getAllStates( ) to avoid unnecessarily repeating code. The code in Example 5-4 can go in a file called StatesEnum.cfc in the package com.oreilly.frdg.

Example 5-4. Returning a query with StatesEnum.cfc
<cfcomponent>
  <cffunction name="getAllStates" access="remote" returnType="query">
    <cfquery datasource="Northwind" name="allStates">
      SELECT StateID, StateName, StateAbbr, StateRegion 
      FROM USStates
    </cfquery>
    <cfreturn #allStates# />
  </cffunction>
  <cffunction name="getStatesByRegion" access="remote" returnType="query">
    <cfargument name="region" type="string" required="true" />
    <cfset allStates=this.getAllStates( ) />
    <cfquery dbtype="query" name="regionalStates">
      SELECT * 
      FROM allStates 
      WHERE StateRegion = '#region#'
    </cfquery>
    <cfreturn #regionalStates# />
  </cffunction>
</cfcomponent>

In order for Example 5-4 to work, you must have an appropriate data source configured through the ColdFusion administrator. A .csv file named USStates.csv filled with a listing of U.S. states and regions is available from the online Code Depot and can be imported to your database of choice for use in this example. Import the file to a new table called USStates.

The StatesEnum.cfc component takes advantage of a nice feature of ColdFusion called queries of queries. The getAllStates( ) method returns a Query object that was returned from the <cfquery> tag used to query the database. Rather than have the getStatesByRegion( ) method use its own <cfquery> tag to run the same query, we can extract the subset of interest by performing a more specific query on the allStates Query object returned by getAllStates( ). Using ColdFusion's query of query capability is efficient in terms of both performance and coding practice.

Example 5-5 shows the client-side ActionScript code for invoking the getAllStates( ) and getStatesByRegion( ) functions remotely.

Example 5-5. ActionScript code for StatesEnum.fla
NetServices.setDefaultGatewayUrl("http://localhost:8500/flashservices/gateway");
var con = NetServices.createGatewayConnection( );
var statesSevice = con.getService("com.oreilly.frdg.StatesEnum", this);
statesService.getAllStates( );
statesService.getStatesByRegion("south");

function onResult (states) {
  // states_cb is the instance name of a ComboBox UI component.
  states_cb.setDataProvider(states);
}

The states parameter passed to the onResult( ) function is cast (or transformed) into an ActionScript RecordSet object by the Flash Remoting gateway. Notice how the function is not concerned with whether it is being passed a recordset of all the U.S. states or a subset based on region. Its only job is to populate a ComboBox with the data it receives. Because we can use the same responder object for calls to both getAllStates( ) and getStatesByRegion( ), we again are able to reuse code.

ColdFusion supports cached queries, which allow data to be held in memory on the server. The allStates query in Example 5-4 is an ideal candidate for caching. To cache a query, define a cachedwithin attribute such as cachedwithin="#CreateTimeSpan(7,0,0,0)#", which caches the query in memory for 7 days. Therefore, the query executes no more than once per week, unless the server restarts (depending on your CF administrative settings, which can limit the number of cached queries).

Although you can access arguments to a component function using the Flash.Params array or as named parameters, you should use <cfargument> tags instead.

There is also an additional variable scope you can use with components, called the arguments scope. The arguments scope can be used with dot notation (arguments.argumentName) or the Structure model (arguments["argumentName"]). If you are going to use a variable scope, you should use the arguments scope instead of the Flash scope for two reasons:

  • It keeps your components generic so that they can be invoked by clients other than remote Flash applications.

  • It is easier to keep track of the variables that are prepended with their scopes. For example, if the getStatesByRegion( ) function is very long, referring to the region argument as arguments.region makes your code clearer. Even if your function contains <cfargument> tags, it's a good idea to use the arguments scope for the sake of readability.

5.3.4.3 Wrapping a Java class in a ColdFusion Component

Although ColdFusion does not support creating or calling methods on Java objects directly, you can easily create CFCs or ColdFusion pages that can delegate to Java objects. That is, you can use a CFC as a thin layer between your Flash Remoting application and the Java object layer. Chapter 7 covers Java and Flash Remoting integration in detail, but here is a short, simple example in the context of a CFC. This example demonstrates the following process:

  1. The Flash client invokes a remote CFC function.

  2. The CFC function instantiates a Java object, passing it the argument that was sent from the Flash client.

  3. The CFC calls a method on an instance of the Java class, which returns a string.

  4. The string returned from the Java method is returned by the CFC to the Flash client.

Figure 5-2 illustrates the process of calling a Java object wrapped in a ColdFusion Component.

Figure 5-2. Wrapping a Java object in a ColdFusion Component
figs/frdg_0502.gif

There are three parts to this example:

  • The client-side ActionScript code

  • The ColdFusion Component

  • The Java object

Let's work backward and start with the Java object.

The Java object, called StringReverser and shown in Example 5-6, has a constructor that accepts a String object. There is only one method on StringReverser, called getReversedString( ), which reverses the order of the characters in the string and returns it as a new string.

Example 5-6. Java class StringReverser.java
package com.oreilly.frdg;

public class StringReverser {
  private String target;

  public StringReverser (String target) {   
    this.target = target;
  }

  public String getReversedString ( ) {   
    StringBuffer reversedString = new StringBuffer( );
    char[] chars = target.toCharArray( );
    for (int i = chars.length; i > 0; --i) {   
      reversedString.append(chars[i-1]);
    }
    return reversedString.toString( );
  }
}

You can compile the StringReverser.java file with any Java IDE or with the command-line compiler javac.exe. Once the Java file is compiled, place the resulting .class file in any directory included in ColdFusion's classpath. In a typical installation of ColdFusion MX on a Windows machine, this would be at C:\CFusionMX\runtime\servers\lib.

If you choose to put your classes in a package, remember to add the package declaration to the top of the .java file, as we have done here, and to create the appropriate directory structure. The StringReverser.class should go into a directory structure of classpath\com\oreilly\frdg. You can also put your class files into a Java archive (.jar) file, as long as the .jar files are included in ColdFusion's classpath.

Let's look at the CFC that serves as a proxy between the Flash client and the StringReverser Java object. The code in Example 5-7 is contained in a file called JavaExamples.cfc, located in the package com.oreilly.frdg.

Example 5-7. JavaExamples.cfc
<cfcomponent>
  <cffunction name="reverseString" access="remote" returnType="string">
    <cfargument name="target" type="string" required="true">
    <cfobject type="Java" action="create" class="StringReverser" 
     name="reverserClass" />
    <cfset reverser = reverserClass.init(#target#) />
    <cfset reversedString = reverser.getReversedString( ) />
    <cfreturn #reversedString# />
  </cffunction>
</cfcomponent>

We use the <cfobject> tag to get a reference to the StringReverser class, not an instance of StringReverser. At this point, you have access to only static members of the class. The instance of StringReverser is actually created and returned from the init( ) method call on the line following the <cfobject> tag.

The StringReverser class does not have an explicit init( ) method. init( ) is a ColdFusion function that is required to be called whenever instantiating a Java object. Calling init( ) from ColdFusion invokes the corresponding class constructor. If you attempt to reference a nonstatic member of a Java object before calling an init( ) method, the object's default no-argument constructor is called if one exists. If the object does not have any constructors at all, allowing the default no-argument constructor to be called in this manner is fine. However, if your object has one or more constructors and does not explicitly define a no-argument constructor, attempting to access nonstatic members before initializing the Java object results in an exception being thrown and propagated up to the Flash client.

By passing the target variable into the init( ) method, StringReverser's constructor is called, returning an instance of StringReverser, which has the value of target set as a member variable. The instance is assigned to the ColdFusion variable reverser, which is the instance that you call methods on. Calling getReversedString( ) on reverser returns the value of target, except with its characters in reverse order, which is assigned to the ColdFusion variable reversedString. We then return reversedString, which, in this case, is returned to the Flash client. The instance of StringReverser goes out of scope and is ready for garbage collection at the moment the CFC function returns. In the case of ColdFusion pages and CFCs, instances of Java objects go out of scope when the entire page has finished executing.

Now let's take a look at the client-side ActionScript for this exercise, shown in Example 5-8.

Example 5-8. ActionScript code for JavaExample1.fla
NetServices.setDefaultGatewayURL("http://localhost:8500/flashservices/gateway");
var my_conn = NetServices.createGatewayConnection( );
var javaExService = my_conn.getService("com.oreilly.frdg.JavaExamples", this);
javaExService.reverseString("this is a top secret code");

function onResult (response) {
  trace(response);
}

function onStatus (error) {
  trace("error: " + error.description);
}

The ActionScript code for this example is straightforward. Calling the remote reverseString( ) function on JavaExamples.cfc returns the reversed string to the onResult( ) callback function. The result should be the string "edoc terces pot a si siht" printed in your Output window.

5.3.5 ColdFusion Component Introspection

Like Java class definitions, CFCs are self-documenting, which means that the components themselves?or, more precisely, the cfexplorer.cfc located in wwwroot/CFIDE/componentutils?can describe how each component is used. A component's ability to reflect upon itself is referred to as introspection or component metadata. There are two primary advantages to component metadata:

  • It exposes components' application programming interface (API) without exposing the implementation of the component.

  • It is always up-to-date.

Clients using your components should not care or rely upon how a function is implemented; they should be concerned only with how a function is used and what data it returns. Hiding the internal implementation of a component is a key requirement of abstraction and encapsulation. Component metadata is a great way for developers to access the information they need without looking through the component's code, which not only defeats the goal of encapsulation but also takes a great deal of time.

Component metadata also allows CFC developers to concentrate on their code, rather than manually keeping the documentation current. Component metadata is generated as HTML, so it can be viewed by simply referencing the .cfc file's URL directly from a web browser, like this:

http://localhost/com/oreilly/frdg/MyCFC.cfc

When the ColdFusion Server receives a request for a CFC file rather than a request to invoke a particular function within it, the request is redirected to webroot\CFIDE\componentutils\cfexplorer.cfc. For security reasons, you are prompted to enter the ColdFusion Remote Development Security (RDS) password, after which you should see a document similar to Figure 5-3, which shows the documentation for StatesEnum.cfc from Example 5-4.

Figure 5-3. Introspecting StatesEnum.cfc
figs/frdg_0503.gif

ColdFusion metadata includes the following information:

  • The inheritance tree of the component

  • The directory the component is in

  • The package the component is in

  • The display name of the component, if the displayName attribute is present

  • Each property that the component contains

  • All the functions the component contains

  • What arguments each function accepts, the arguments' datatypes, and whether they are required

  • The ColdFusion variable name used to reference each argument

  • Arguments' default values, if present

  • What type of access each function allows

  • The value of the function's hint attribute, if present

  • Whether output is enabled for each function

  • The datatype of the return value of each function

If you are not sure which component you are looking for, you can use the Component Browser to browse all the components on the ColdFusion Server. To access the Component Browser, enter a URL of the following form in your web browser:

wwwroot/CFIDE/componentutils/componentdoc.cfm

For example:

http://localhost:8500/CFIDE/componentutils/componentdoc.cfm

Dreamweaver MX also allows you to introspect CFCs from its Components panel. In Dreamweaver MX, after you've defined a site and set your server model to ColdFusion, the Components panel displays all components that are available on the server, in a tree, as shown in Figure 5-4. Right-clicking (in Windows) or Ctrl-clicking (on the Macintosh) on a component, method, or argument within the panel gives details about that particular item.

Figure 5-4. Introspecting the Email.cfc component from within Dreamweaver MX
figs/frdg_0504.gif

You can also create your own interface for introspecting CFCs.

5.3.5.1 Discovering ColdFusion Components from Flash

In addition to being able to browse CFC services from your browser and from Dreamweaver MX, you can also browse them directly from the Flash authoring environment through the Service Browser (Window Service Browser). You cannot discover unknown services, because you must enter the address of the service in order to find it; however, it is a convenient way to keep important component APIs available while you write ActionScript code against them. Chapter 2 describes the Service Browser, which is shown in Figures Figure 2-4 and Figure 5-5.

The Description field



Part III: Advanced Flash Remoting