14.4 The Modules

With the specifications in place, we can focus on the implementation. We'll document first and code later, and we'll use OOP techniques for much of the application. One advantage of this approach is that the specifications dictate how the coding takes place. For example, we have specified that we will have users and scripts; these elements can be implemented as objects. This makes the coding process more applicable to real-world situations. We know the different properties of a user and the different properties of a script (outlined in the specifications), so these will be the properties of our objects. Even though we are using some OOP techniques and some objects, the application is not strictly an object-oriented application.

14.4.1 Structure

We will build the overall structure before we set out to code the functionality of the application. I've found that this is often the best way to approach a problem. You can think of it like drawing a picture: if you draw the outline first, it is a lot easier to color in, rather than color the picture and then try to draw the outline around it after the fact. In this way, comments and function skeletons make up your outline, and the actual code is used to "color in" the program. This has the added benefit that the comments are finished when your code is finished, rather than requiring you to add comments at the end of the project.

The skeleton code should be fully working code. Even placeholder functions should include return statements so that the program works as you code.

14.4.2 Database

The database is the first physical structure to create. The database needs to be in place and functional before the application can be built. The database structure is shown in Tables Table 14-1 through Table 14-5. Table 14-1 shows the Users table, which is used to manage user login.

Table 14-1. Users table

Column name

Datatype

Length

Notes

UserID

integer

4

Auto-numbering, primary key

Username

text

16

 

Password

text

12

 

FirstName

text

60

 

LastName

text

60

 

EmailAddress

text

255

 

HintQuestion

text

255

Prompt the user if password is forgotten

HintAnswer

text

20

Verify user response if password is forgotten

Table 14-2 shows the Categories table, which is used to group scripts into categories for easier searching and sorting once the repository grows larger.

Table 14-2. Categories table

Column name

Datatype

Length

Notes

CategoryID

integer

4

Autonumbering, primary key

CategoryDesc

text

60

 

Table 14-3 shows the Scripts table, which is used to manage the contributed scripts.

Table 14-3. Scripts table

Column name

Datatype

Length

Notes

ScriptID

integer

4

Autonumbering, primary key

ScriptName

text

60

 

ScriptDescription

text

255

 

ScriptCode

text

4095

 

LanguageID

integer

4

Foreign key to Languages table

CategoryID

integer

4

Foreign key to Categories table

UserID

integer

4

Foreign key to Users table

DateUploaded

date/time

8

Defaults to current date

DateModified

date/time

8

Defaults to current date

VersionMajor

integer

4

Defaults to 1

VersionMinor

integer

4

Defaults to 0

VersionMicro

integer

4

Defaults to 0

ScriptUniqueID

Unique identifier (UUID)

36

 

Table 14-4 shows the Languages table, which is used to track the programming languages in which scripts are written.

Table 14-4. Languages table

Column name

Datatype

Length

Notes

LanguageID

integer

4

Autonumbering, primary key

LanguageName

text

50

 

Table 14-5 shows the CompanyInfo table, which is used to provide contact information for contributors.

Table 14-5. CompanyInfo table

Column name

Datatype

Length

CompanyName

text

60

Address

text

127

City

text

60

State

text

2

Zip

text

9

Phone

text

50

Fax

text

50

ContactFirstName

text

50

ContactLastName

text

50

ContactEmail

text

127

PrivacyPolicy

text

1000

Description

text

1000

The database table specs have been shown in a generic fashion, to allow you to implement them in your own particular database. For example, the text datatypes are implemented as varchar or nvarchar fields in SQL Server or MySQL. Similarly, the DateUploaded field in the Scripts table is implemented as a datetime field, with a default value of getdate( ) in SQL Server or current_date( ) in MySQL. Other database implementations will vary.

The completed database diagram of table relationships is shown in Figure 14-1.

Figure 14-1. The completed database diagram shows table relationships
figs/frdg_1401.gif

14.4.3 Defining Server-Side Services

The server-side services are implemented with ColdFusion Components. The required services are shown in Tables Table 14-6 through Table 14-8.

Table 14-6 lists the service methods of the UserService service.

Table 14-6. The UserService service

Service method

Description

Arguments

Returns

loginUser( )

Validates username and password against the database. Sets the session if the login is successful, and sets the user's access level.

Username (string), Password (string)

Userid (numeric)

addUser( )

Adds a new user to the database. If the registration is successful, the user is also automatically logged in.

UserObject

UserObject

emailPassword( )

Emails a password to a user if he forgets his password.

EmailAddress

True

createUserObj( )

Package method that creates an object of type UserObject for passing back to ActionScript.

FirstName, LastName, EmailAddress, Username, Userpassword, HintQuestion, HintAnswer

UserObject

getEmail( )

Gets the user's hint question for retrieving a password.

EmailAddress

Hint question (string)

getScriptsForUser( )

Gets all scripts submitted by logged-in user.

UserID (numeric)

Recordset

Table 14-7 lists the service methods of the ScriptService service.

Table 14-7. The ScriptService service

Service method

Description

Arguments

Returns

addScript( )

Adds a script to the database.

ScriptObject

Script id (numeric)

updateScript( )

Updates an existing script in the database.

ScriptObject

ScriptObject

displayScript( )

Displays the script on the screen.

ScriptID (numeric)

ScriptObject

displayList( )

Displays a list of available scripts, with clickable links.

Search word (optional)

Recordset

getScript( )

Gets all information about a script to display.

ScriptID (numeric)

ScriptObject

createScriptObj( )

Package method that creates an object of type ScriptObject for passing back to ActionScript.

ScriptID, ScriptName, ScriptDescription, ScriptCode, LanguageID, CategoryID, UserID, DateUploaded, DateModified, VersionMajor, VersionMinor, VersionMicro, ScriptUniqueId

ScriptObject

DateTimeString( )

Package method that converts a Date object from ActionScript into a human-readable date/time string

Date object or string

Formatted date string

Table 14-8 lists the service methods of the SiteService service.

Table 14-8. The SiteService service

Service method

Description

Arguments

Returns

about( )

Returns a short paragraph about the company from the database.

None

RecordSet

contactForm( )

Contacts the site administrator by email through a standard form.

UserID (numeric), Comment (string)

true

sendPage( )

Sends the page information to a friend.

UserID (numeric), Email address (string), Script ID (numeric)

true

getCategories( )

Retrieves a list of all categories for drop-down list.

None

RecordSet

getLanguages( )

Retrieves a list of all languages for drop-down list.

None

RecordSet

getUsers( )

Retrieves a list of all users for drop-down list.

None

RecordSet

Using Dreamweaver MX, you can create skeletons for all of the services. Dreamweaver MX allows you to create CFCs using an interface (shown in Figure 14-2), with function skeletons in place.

Figure 14-2. The Dreamweaver MX component interface
figs/frdg_1402.gif

Example 14-1 lists the skeleton code for the UserService service.

Example 14-1. Autogenerated skeleton code for the UserService service
<!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:07:39 GMT-0800 
(Pacific Standard Time) 2003 --->

<cfcomponent displayName="UserService">
  <cffunction name="loginUser" displayName="loginUser"
    hint="Logs a user into the script repository"
    access="remote" returnType="string" output="false">
      <cfargument name="username" type="string" required="true">
      <cfargument name="password" type="string" required="true">
      <!--- loginUser body --->
      <cfreturn >
  </cffunction>
  <cffunction name="addUser" displayName="addUser"
    hint="Add a user to the database" access="remote"
    returnType="string" output="false">
      <cfargument name="Username" type="string" required="true">
      <cfargument name="FirstName" type="string" required="true">
      <cfargument name="LastName" type="string" required="true">
      <cfargument name="EmailAddress" type="string" required="true">
      <cfargument name="Password" type="string" required="true">
      <cfargument name="HintQuestion" type="string" required="false">
      <cfargument name="HintAnswer" type="string" required="false">
      <!--- addUser body --->
      <cfreturn >
  </cffunction>
  <cffunction name="emailPassword" displayName="emailPassword"
    hint="Email a password to a user, given the email address"
    access="remote" returnType="string" output="false">
      <cfargument name="EmailAddress" type="string" required="true">
      <cfargument name="HintQuestion" type="string" required="true">
      <!--- emailPassword body --->
      <cfreturn >
  </cffunction>
  <cffunction name="createUserObj" displayName="createUserObj"
    hint="Create ActionScript object to hold user information"
    access="package" returnType="struct" output="false">
      <cfargument name="Username" type="string" required="true">
      <cfargument name="FirstName" type="string" required="true">
      <cfargument name="LastName" type="string" required="true">
      <cfargument name="EmailAddress" type="string" required="true">
      <cfargument name="Password" type="string" required="true">
      <cfargument name="HintQuestion" type="string" required="false">
      <cfargument name="HintAnswer" type="string" required="false">
      <!--- createUserObj body --->
      <cfreturn >
  </cffunction>
  <cffunction name="getEmail" displayName="getEmail"
    hint="Retrieve the user's hint question given an email address"
    access="remote" returnType="string" output="false">
      <cfargument name="EmailAddress" type="string" required="true">
      <!--- getEmail body --->
      <cfreturn >
  </cffunction>
  <cffunction name="getScriptsForUser" displayName="getScriptsForUser"
    hint="Retrieve the user's scripts to feed a combo box"
    access="remote" returnType="recordset" output="false">
      <cfargument name="UserID" type="string" required="true">
      <!--- getEmail body --->
      <cfreturn >
  </cffunction>
</cfcomponent>

The methods of the CFC are each defined with all arguments and an empty return value. As you can see, the method bodies are empty, except for a comment. The code body will go there, but not yet. We'll fill in comments for each method, explaining what the method does, what is required, and what is returned. This will make it that much easier to write the methods afterwards, and the code will be fully commented. An example of a fully commented function skeleton is shown in Example 14-2. The component skeletons can be downloaded from the online Code Depot.

Example 14-2. The fully commented displayList( ) method skeleton
<cffunction name="displayList"
 access="remote"
 returnType="query"
 output="false">
<!---
    Method: displayList
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      search      Optional search criteria
    Return: query object of all script information. Properties are
      ScriptID    ID number of the script (primary key)
      Category    The category name
      CategoryID  The categoryID
      ScriptName  The name of the script
    Description:
      This service returns a complete list of scripts available or a list
      that meets the search criteria
--->
    <!--- displayList body --->
    <!--- End displayList body --->
    <cfreturn />
</cffunction>

Test mechanisms (also known as test harnesses) can be set up as plain ColdFusion pages to test that each service and each method is working. Inside of the Dreamweaver MX environment, simply drag the CFC from the Components panel onto a .cfm page and insert a form and conditional logic to test the form, as in the test page shown in Example 14-3.

Example 14-3. A test page for the UserService service
<cfparam name="form.test" default="" />
<html>
<head>
<title>User Service Test Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>

<body>
<form name="form1" method="post" action="">
  <select name="test" id="test">
    <option value="addUser">addUser</option>
    <option value="emailPassword">emailPassword</option>
    <option value="loginUser">loginUser</option>
  </select>
  <input type="submit" name="Submit" value="Submit">
</form>
<cfif form.test EQ "addUser">
<cfinvoke
  component="com.oreilly.frdg.ScriptRepository.UserService"
  method="addUser"
  returnvariable="addUserRet">
    <cfinvokeargument name="Username" value="enter_value_here"/>
    <cfinvokeargument name="FirstName" value="enter_value_here"/>
    <cfinvokeargument name="LastName" value="enter_value_here"/>
    <cfinvokeargument name="EmailAddress" value="enter_value_here"/>
    <cfinvokeargument name="Password" value="enter_value_here"/>
    <cfinvokeargument name="HintQuestion" value="enter_value_here"/>
    <cfinvokeargument name="HintAnswer" value="enter_value_here"/>
</cfinvoke>
<cfoutput>#addUserRet#</cfoutput>
</cfif>

<cfif form.test eq "emailPassword">
<cfinvoke
  component="com.oreilly.frdg.ScriptRepository.UserService"
  method="emailPassword"
  returnvariable="emailPasswordRet">
    <cfinvokeargument name="EmailAddress" value="enter_value_here"/>
    <cfinvokeargument name="HintAnswer" value="enter_value_here"/>
</cfinvoke>
<cfoutput>#emailPasswordRet#</cfoutput>
</cfif>

<cfif form.test eq "loginUser"><cfinvoke
  component="com.oreilly.frdg.ScriptRepository.UserService"
  method="loginUser"
  returnvariable="loginUserRet">
    <cfinvokeargument name="username" value="enter_value_here"/>
    <cfinvokeargument name="password" value="enter_value_here"/>
</cfinvoke>
<cfoutput>#loginUserRet#</cfoutput>
</cfif>

</body>
</html>

If you build pages like these to test each server-side service, they will be invaluable in determining where problems might occur before you begin to bring Flash into the equation. Using a page like this in ColdFusion gives you full access to ColdFusion debugging and also allows you to easily manipulate the parameters and return values to test different situations.

14.4.4 Implementing Server-Side Services

With the server-side service skeletons in place, you can begin to flesh out the services. If you have built ColdFusion test pages as recommended, you can test the services one by one as you build them.

The services use a data source name called ScriptRepository, using the sample database available for download from http://www.flash-remoting.com. You must set this data source name up in your ColdFusion Administrator in order to create the server-side services.

14.4.4.1 The UserService service

The UserService service implements all methods that relate to users, such as logging in and retrieving passwords. You can easily add more methods to the service as the application becomes more advanced. In addition to the remote methods available to the Flash movie, there is a package method called createUserObj( ) that is used internally by some of the methods to create an object of type UserObject to pass back to ActionScript.

The completed code for the UserService remote service is shown in Example 14-4. Refer to Table 14-6 for a summary of the service methods for this service.

Example 14-4. The UserService service, implemented as UserService.cfc
<!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:07:39 GMT-0800 
(Pacific Standard Time) 2003 --->

<cfcomponent displayName="UserService">
<!---
  Service:  UserService
  Package:   com/oreilly/frdg/ScriptRepository
  Description: Services to interact with Users from the
               ScriptRepository application
--->
  <cffunction name="createUserObj" displayName="createUserObj"
    hint="Create ActionScript object to hold user information"
    returnType="struct" access="package" output="false">
    <!--- Create the ActionScript object --->
    <cfobject type="java"
      class="flashgateway.io.ASObject"
       name="UserObject"
       action="create" />
    <!--- Create an instance of the object --->
     <cfset o = UserObject.init( )>
    <!--- Set the type to our custom UserObjectClass for deserialization --->
     <cfset o.setType("UserObject")>

    <cfset o.put("UserID", arguments[1]) />
    <cfset o.put("Username", arguments[2]) />
    <cfset o.put("Userpassword", arguments[3]) />
    <cfset o.put("FirstName", arguments[4]) />
    <cfset o.put("LastName", arguments[5]) />
    <cfset o.put("Emailaddress", arguments[6]) />
    <cfset o.put("HintQuestion", arguments[7]) />
    <cfset o.put("isUserLogged", 1) />
    <cfset o.put("inited", 1) />
    <cfreturn o />
  </cffunction>

  <cffunction name="loginUser" displayName="loginUser"
    hint="Logs a user into the script repository"
    access="remote" returnType="any" output="false">
  <!---
    Method: loginUser
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      username  string of up to 16 characters
      password  string of up to 12 characters

    Return: user object
    Description:
      This service allows the user to log in to the application by
      verifying the username/password in the database and returning
      all of the properties of the user to the Flash movie.
  --->
    <cfargument name="username" type="string" required="true">
    <cfargument name="userpassword" type="string" required="true">
    <!--- loginUser body --->
    <cftry>
      <cfquery datasource="ScriptRepository"
        name="rsUserLogin">
        SELECT * FROM Users
        WHERE Username =
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">
         AND Password =
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#userpassword#">
      </cfquery>
      <cfcatch type="Any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>

    <cfif rsUserLogin.RecordCount GT 0 >
      <cfset UserObj = createUserObj(rsUserLogin.UserID,
          rsUserLogin.Username,
          rsUserLogin.Password,
          rsUserLogin.FirstName,
          rsUserLogin.LastName,
          rsUserLogin.Emailaddress,
          rsUserLogin.HintQuestion
          ) />
    <cfelse>
      <cfthrow message="Not a valid user" />
    </cfif>
    <!--- end loginUser body --->

    <cfreturn UserObj />
  </cffunction>

  <cffunction name="addUser" displayName="addUser"
   hint="Add a user to the database" access="remote"
   returnType="any" output="false">
  <!---
    Method: addUser
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      FirstName  string of up to 60 characters
      LastName  string of up to 60 characters
      EmailAddress  string of up to 127 characters
      Username  string of up to 16 characters
      Userpassword  string of up to 12 characters
      HintQuestion  string of up to 255 characters
      HintAnswer  string of up to 20 characters
    Return: user object
    Description:
      This service allows a new user to be added to the database,
      and automatically to log in to the application by
      verifying the username/password in the database and returning
      all of the properties of the user to the Flash movie.
  --->
    <cfargument name="Username" type="string" required="true">
    <cfargument name="FirstName" type="string" required="true">
    <cfargument name="LastName" type="string" required="true">
    <cfargument name="EmailAddress" type="string" required="true">
    <cfargument name="Userpassword" type="string" required="true">
    <cfargument name="HintQuestion" type="string" required="false">
    <cfargument name="HintAnswer" type="string" required="false">
    <!--- addUser body --->
    <cftry>
      <cfquery datasource="ScriptRepository"
        name="rsDoesUserExist">
        SELECT * FROM Users
         WHERE Username =
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">
      </cfquery>
      <cfcatch type="Any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
    <cfif rsDoesUserExist.RecordCount EQ 0>
      <cftry>
        <cfquery datasource="ScriptRepository"
         name="rsAddUser">
           INSERT INTO Users
           (Username, Password, FirstName, LastName,
           EmailAddress, HintQuestion, HintAnswer)
           VALUES (
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#Username#">,
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#Userpassword#">,
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#FirstName#">,
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#LastName#">,
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">,
        <cfqueryparam cfsqltype="cf_sql_varchar"
          null="#HintQuestion EQ ''#" value="#HintQuestion#">,
        <cfqueryparam cfsqltype="cf_sql_varchar"
          null="#HintAnswer EQ ''#" value="#HintAnswer#">
          )
        </cfquery>
        <cfcatch type="Any">
          <cfthrow message="There was a database error" />
        </cfcatch>
      </cftry>
    <cfelse>
      <cfthrow message="Already a user with that username" />
    </cfif>

    <!--- End addUser body --->
    <cfreturn this.loginUser('#username#','#userpassword#') />
  </cffunction>

  <cffunction name="getEmail" displayName="getEmail"
    hint="Retrieve the user's hint question given an email address"
    access="remote" returnType="string" output="false">
  <!---
    Method: getEmail
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      EmailAddress  string of up to 127 characters
    Return: Hint question (string)
    Description:
      This service retrieves the user's hint question given an email
      address
  --->
    <cfargument name="EmailAddress" type="string" required="true">
    <!--- getEmail body --->
     <cftry>
      <cfquery datasource="ScriptRepository"
       name="rsGetQuestion">
         SELECT HintQuestion FROM Users
         WHERE Emailaddress =
         <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">
      </cfquery>
      <cfcatch type="Any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>

    <cfif rsGetQuestion.RecordCount NEQ 1>
      <cfthrow message="No match found in database" />
    </cfif>
    <!--- End getEmail body --->
    <cfreturn rsGetQuestion.HintQuestion />
  </cffunction>

  <cffunction name="emailPassword" displayName="emailPassword"
   hint="Email a password to a user, given the email address"
   access="remote" returnType="boolean" output="false">
  <!---
    Method: emailPassword
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      EmailAddress  string of up to 127 characters
      HintAnswer  string of up to 20 characters
    Return: boolean of successful email of the password
    Description:
      This service allows a user to have his password emailed to him,
      if the hint answer matches the user's hint in the database.
  --->
    <cfargument name="EmailAddress" type="string" required="true">
    <cfargument name="HintAnswer" type="string" required="true">
    <!--- emailPassword body --->
    <cftry>
      <cfquery datasource="ScriptRepository"
       name="rsGetUser">
         SELECT Username, Password FROM Users
         WHERE Emailaddress =
         <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">
         AND HintAnswer =
         <cfqueryparam cfsqltype="cf_sql_varchar" value="#HintAnswer#">
      </cfquery>
       <cfcatch type="Any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>

    <cfif rsGetUser.RecordCount EQ 1>
    <!---Only send the email if there is a matching record in the database --->
      <cfmail from="admin@flash-remoting.com" to="#Emailaddress#"
       subject="Requested information">
Your username is: #rsGetUser.username#
Your password is: #rsGetUser.password#
Please respond to admin@flash-remoting.com if you have received
this message in error.

Administrator
      </cfmail>
    <cfelse>
      <cfreturn 0 />
    </cfif>
    <!--- End emailPassword body --->
    <cfreturn 1 />
  </cffunction>

  <cffunction name="getScriptsForUser" access="remote"
   returnType="query" output="false">
    <!---
    Method: getScriptsForUser
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      Username     username for currently logged-in user
      Userpassword     password for currently logged-in user
    Return:
      query object of scriptid and scriptname information.
    Description:
      This service returns a complete list of scripts available
      for the currently logged in user.
  --->
    <cfargument name="username" hint="Username of current user"
     type="string"  default="" />
    <cfargument name="userpassword" hint="Password of current user"
     type="string"  default="" />
    <!--- getScriptsForUser body --->
    <cftry>
      <cfquery name="rsScripts" datasource="ScriptRepository">
        SELECT s.ScriptID, s.ScriptName
        FROM Users u, Scripts s
        WHERE u.Username =
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">
        AND u.Password =
        <cfqueryparam cfsqltype="cf_sql_varchar" value="#userpassword#">
        AND s.UserID = u.UserID
        ORDER BY s.ScriptName
      </cfquery>
      <cfcatch type="any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
    <!--- End getScriptsForUser body --->
    <cfreturn rsScripts />
  </cffunction>
</cfcomponent>

The UserService service interacts with the Flash movie using a UserObject, a custom object that we set up in ActionScript using Object.registerClass. As discussed in Chapter 4, this method of transferring objects allows for seamless passing of data between client and server. ColdFusion supports a <cfobject> tag, which allows you to set up the object as a Java object of type flashgateway.io.ASObject.

There are a few things of note in the code. All queries that contain user-supplied parameters are set up with a <cfqueryparam> tag. This tag guards the application against SQL injection attacks, in which a malicious user sends SQL statements in a URL to attempt to damage your data or even gain control of your database.

Because we control the input from the Flash movie, you might think that the remote methods are safe, but that is not the case. A remote service that is set up for Flash Remoting is completely open to the outside world. A person can interact with your remote service if he knows the URL and the service name, which can easily be obtained from the Flash movie by decompiling it. A remote service can then be invoked through a URL, making all remote services that accept parameters open to attack.

The following query, from the getEmail( ) method, demonstrates the use of the <cfqueryparam> tag:

<cfquery datasource="ScriptRepository"
 name="rsGetQuestion">
  SELECT HintQuestion FROM Users
  WHERE Emailaddress =
  <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">
</cfquery>

The <cfqueryparam> tag takes the place of the parameter within the query and throws an error if the datatype is not right. Therefore, the parameter is usable only as a proper parameter; crackers cannot inject SQL statements into the query.

The <cfqueryparam> tag has a counterpart if you are using stored procedures as well: <cfprocparam>. All queries and stored procedures that accept user-supplied parameters should use these tags.

Also, the queries in the page are all wrapped in try/catch blocks, in order to trap errors and simply throw them back to the Flash movie. We could put some other form of error handling within the block, such as writing to a log file or sending an email to a site administrator, but this method is the simplest.

The <cfmail> tag contains the body of the email message to be sent to the end user. For that reason, the text is aligned flush left, even though the code is nicely indented otherwise. The <cfmail> tag translates any spaces or line breaks within the body of the message literally, so the text format should be preserved inside the tag.

One last item deserves a mention: when a user is added to the database, the user is also automatically logged in, as shown in the following line from the addUser( ) method:

<cfreturn this.loginUser('#username#','#userpassword#') />

Rather than simply return a Boolean value indicating whether the user was successfully added to the database, we log the user in using the UserService.loginUser( ) method and return the UserObject to the Flash movie. This improves the end user's experience because she doesn't need to log in as a separate step after registering.

14.4.4.2 The ScriptService service

The ScriptService service includes methods that relate to the storing of the scripts. The service contains many of the same types of features that the UserService.cfc file had, such as the package method that creates a ScriptObject object type, and the use of the <cfqueryparam> tags to guard against malicious user input.

The completed ScriptService service is shown in Example 14-5. Refer to Table 14-7 for a summary of the service methods for this service.

Example 14-5. The ScriptService service, implemented as ScriptService.cfc
<!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:32:00 GMT-0800 
(Pacific Standard Time) 2003 --->
<cfcomponent displayName="ScriptService">
  <!---
  Service:  ScriptService
  Package:   com/oreilly/frdg/ScriptRepository
  Description: Utilizes a Script object to pass information back and forth
    from the Flash movie.
  --->
  <cffunction name="createScriptObj" hint="Create ActionScript object to
    hold script information" returnType="struct" access="package" output="false">
    <!--- Create the ActionScript object --->
    <cfobject type="java"
     class="flashgateway.io.ASObject"
     name="ScriptObject"
     action="create" />
    <!--- Create an instance of the object --->
     <cfset o = ScriptObject.init( )>
    <!--- Set the type to our custom UserObjectClass for deserialization --->
    <cfset o.setType("ScriptObject")>
    <cfset o.put("ScriptID", arguments[1]) />
    <cfset o.put("ScriptName", arguments[2]) />
    <cfset o.put("ScriptDescription", arguments[3]) />
    <cfset o.put("ScriptCode", arguments[4]) />
    <cfset o.put("LanguageID", arguments[5]) />
    <cfset o.put("CategoryID", arguments[6]) />
    <cfset o.put("UserID", arguments[7]) />
    <cfset o.put("DateUploaded", this.DateTimeString(arguments[8])) />
    <cfset o.put("DateModified", this.DateTimeString(arguments[9])) />
    <cfset o.put("VersionMajor", arguments[10]) />
    <cfset o.put("VersionMinor", arguments[11]) />
    <cfset o.put("VersionMicro", arguments[12]) />
    <cfset o.put("ScriptUniqueID", arguments[13]) />
    <cfset o.put("inited",1) />
    <cfreturn o />
  </cffunction>

  <cffunction name="addScript" access="remote" returnType="any" output="false">
    <!---
    Method: addScript
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      ScriptObj  a script object with all properties needed to add
      the script to the database. Properties are:
        ScriptName
        ScriptDescription
        ScriptCode
        LanguageID
        CategoryID
        UserID
        DateUploaded
        DateModified
        VersionMajor
        VersionMinor
        VersionMicro
    Return: scriptid
    Description:
      This service allows a registered user to upload a script
      to the database.
  --->
    <!--- <cfargument name="ScriptObj" type="struct" required="true"> --->
    <!--- AddScript body --->

  <!--- Create a unique ID to aid in retrieving the primary key --->
  <cfset scriptuniqueid = CreateUUID( ) />
  <cfset ScriptObj = this.createScriptObj(
    0,
    arguments.ScriptName,
    arguments.ScriptDescription,
    arguments.ScriptCode,
    arguments.LanguageID,
    arguments.CategoryID,
    arguments.UserID,
    arguments.DateUploaded,
    arguments.DateModified,
    arguments.VersionMajor,
    arguments.VersionMinor,
    arguments.VersionMicro,
    scriptuniqueid
    ) />
  <!--- Insert the script into the database --->
     <cftry>
     <cfquery name="insertScript" datasource="ScriptRepository">
      INSERT INTO Scripts (
        ScriptName,
      ScriptDescription,
      ScriptCode,
      LanguageID,
      CategoryID,
      UserID,
      DateUploaded,
      DateModified,
      VersionMajor,
      VersionMinor,
      VersionMicro,
      scriptuniqueid
    )
      VALUES (
      <cfqueryparam cfsqltype="cf_sql_varchar"
       null="#ScriptObj.ScriptName EQ ''#"
       value="#ScriptObj.ScriptName#">,
      <cfqueryparam cfsqltype="cf_sql_varchar"
       null="#ScriptObj.ScriptDescription EQ ''#"
       value="#ScriptObj.ScriptDescription#">,
      <cfqueryparam cfsqltype="cf_sql_varchar"
       null="#ScriptObj.ScriptCode EQ ''#"
       value="#ScriptObj.ScriptCode#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#ScriptObj.LanguageID EQ ''#"
       value="#ScriptObj.LanguageID#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#ScriptObj.CategoryID EQ ''#"
       value="#ScriptObj.CategoryID#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#ScriptObj.UserID EQ ''#"
       value="#ScriptObj.UserID#">,
      <cfqueryparam cfsqltype="cf_sql_timestamp"
       null="#ScriptObj.DateUploaded EQ ''#"
       value="#ScriptObj.DateUploaded#">,
      <cfqueryparam cfsqltype="cf_sql_timestamp"
       null="#ScriptObj.DateModified EQ ''#"
       value="#ScriptObj.DateModified#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#ScriptObj.VersionMajor EQ ''#"
       value="#ScriptObj.VersionMajor#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#ScriptObj.VersionMinor EQ ''#"
       value="#ScriptObj.VersionMinor#">,
      <cfqueryparam cfsqltype="cf_sql_numeric"
       null="#(ScriptObj.VersionMicro EQ '')#"
       value="#ScriptObj.VersionMicro#">,
      <cfqueryparam cfsqltype="cf_sql_varchar"
       null="no"
       value="#scriptuniqueid#">
    )
      </cfquery>
    <cfquery name="rsScript" datasource="ScriptRepository">
      SELECT ScriptID FROM Scripts
    WHERE ScriptUniqueID = '#scriptuniqueid#'
      </cfquery>
      <cfcatch type="any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
    <!--- End AddScript body --->
    <cffile action="append" file="c:\log.txt" output=#this.objToString(ScriptObj)#>

    <cfreturn ScriptObj />
  </cffunction>
  <cffunction name="updateScript" access="remote" returnType="any" output="false">
    <!---
    Method: updateScript
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      ScriptObj  a script object with all properties needed to add
      the script to the database. Properties are:
        ScriptID
        ScriptDescription
        ScriptCode
        LanguageID
        CategoryID
        UserID
        DateUploaded
        DateModified
        VersionMajor
        VersionMinor
        VersionMicro
    Return: Updated ScriptObject
    Description:
      This service allows a registered user to change a script that
      exists in the database.
  --->

  <!--- UpdateScript body --->
  <cfset ScriptObj = this.createScriptObj(
    arguments.ScriptID,
    arguments.ScriptName,
    arguments.ScriptDescription,
    arguments.ScriptCode,
    arguments.LanguageID,
    arguments.CategoryID,
    arguments.UserID,
    arguments.DateUploaded,
    arguments.DateModified,
    arguments.VersionMajor,
    arguments.VersionMinor,
    arguments.VersionMicro,
    0
    ) />
  <cftry>
    <cfquery name="updateScript" datasource="ScriptRepository">
         UPDATE Scripts SET ScriptName =
         <cfqueryparam cfsqltype="cf_sql_varchar"
          null="#ScriptObj.ScriptName EQ ''#"
          value="#ScriptObj.ScriptName#">,
         ScriptDescription =
         <cfqueryparam cfsqltype="cf_sql_varchar"
          null="#ScriptObj.ScriptDescription EQ ''#"
          value="#ScriptObj.ScriptDescription#">,
         ScriptCode =
         <cfqueryparam cfsqltype="cf_sql_varchar"
          null="#ScriptObj.ScriptCode EQ ''#"
          value="#ScriptObj.ScriptCode#">,
         LanguageID =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.LanguageID EQ ''#"
          value="#ScriptObj.LanguageID#">,
         CategoryID =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.CategoryID EQ ''#"
          value="#ScriptObj.CategoryID#">,
         UserID =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.UserID EQ ''#"
          value="#ScriptObj.UserID#">,
         DateModified =
         <cfqueryparam cfsqltype="cf_sql_timestamp"
          null="#ScriptObj.DateModified EQ ''#"
          value="#ScriptObj.DateModified#">,
         VersionMajor =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.VersionMajor EQ ''#"
          value="#ScriptObj.VersionMajor#">,
         VersionMinor =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.VersionMinor EQ ''#"
          value="#ScriptObj.VersionMinor#">,
         VersionMicro =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.VersionMicro EQ ''#"
          value="#ScriptObj.VersionMicro#">
         WHERE ScriptID   =
         <cfqueryparam cfsqltype="cf_sql_numeric"
          null="#ScriptObj.ScriptID EQ ''#"
          value="#ScriptObj.ScriptID#">
    </cfquery>
      <cfcatch type="any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
    <!--- End UpdateScript body --->
    <cfreturn ScriptObj />
  </cffunction>
  <cffunction name="displayList" access="remote" returnType="query" output="false">
    <!---
    Method: displayList
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      search     Optional search criteria
    Return: query object of all script information. Properties are
      ScriptID  ID number of the script (primary key)
      Category  The category name
      CategoryID  The categoryID
      ScriptName  The name of the script
    Description:
      This service returns a complete list of scripts available or a list
      that meets the search criteria
    --->
  <cfargument name="search" hint="Search criteria for script listing"
   type="string"  default="" />
    <!--- DisplayList body --->
  <cftry>
      <cfquery name="rsScripts" datasource="ScriptRepository">
        SELECT c.CategoryDesc
        , c.CategoryID
        , s.ScriptID
        , s.ScriptName
        FROM Categories c
        INNER JOIN
        Scripts s ON
        c.CategoryID = s.CategoryID
        <cfif search neq "">
          WHERE s.ScriptDescription + s.ScriptName + c.CategoryDesc
          LIKE
          <cfqueryparam cfsqltype="cf_sql_varchar" value="%#search#%">
        </cfif>
        ORDER BY c.CategoryDesc, s.ScriptID
      </cfquery>
      <cfcatch type="any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
    <!--- End DisplayList body --->
    <cfreturn rsScripts />
  </cffunction>

  <cffunction name="getScript" access="remote" returnType="struct" output="false">
    <!---
    Method: getScript
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      ScriptID  ID number of the script to display
    Return: ScriptObj
    Description:
      This service returns a script object to be displayed in the
      Flash movie
    --->
    <cfargument name="ScriptID" type="any" required="true">
    <!--- DisplayScript body --->
  <cftry>
      <cfquery name="rsScripts" datasource="ScriptRepository">
    SELECT * FROM Scripts
    WHERE ScriptID =
    <cfqueryparam cfsqltype="cf_sql_numeric" value="#ScriptID#">
      </cfquery>
      <cfcatch type="any">
        <cfthrow message="There was a database error" />
      </cfcatch>
    </cftry>
  <cfif rsScripts.RecordCount EQ 1>
   <cfset ScriptObj = createScriptObj(rsScripts.ScriptID,
     rsScripts.ScriptName ,
     rsScripts.ScriptDescription ,
     rsScripts.ScriptCode,
     rsScripts.LanguageID,
     rsScripts.CategoryID,
     rsScripts.UserID,
     rsScripts.DateUploaded,
     rsScripts.DateModified,
     rsScripts.VersionMajor,
     rsScripts.VersionMinor,
     rsScripts.VersionMicro,
     rsScripts.ScriptUniqueID
     ) />
   <cfelse>
     <cfthrow message="No script with that ID" />
   </cfif>
    <!--- End getScript body --->
    <cfreturn ScriptObj />
  </cffunction>

  <cffunction name="DateTimeString" access="package" hint="Convert a date/time data to a 
string for display" returntype="string" >
   <cfargument name="dateObj" type="any" required="false" />
  <cfif isdate(dateObj)>
    <cfset returnstring =
     "#DateFormat(dateObj,'mm/dd/yyyy')# #TimeFormat(dateObj, 'hh:mm:ss tt')#" />
  <cfelse>
    <cfset returnstring = dateObj />
  </cfif>
  <cfreturn returnstring />
  </cffunction>

</cfcomponent>
14.4.4.3 The SiteService service

The SiteService service contains methods to populate UI components, contact the site administrator, send messages to other users, and populate the About screen, as summarized in Table 14-8.

The complete server-side code for the service is shown in Example 14-6.

Example 14-6. The SiteService service, implemented as SiteService.cfc
<!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Thu Jan 30 21:49:32 GMT-0800 
(Pacific Standard Time) 2003 --->

<cfcomponent displayName="SiteService" hint="General service for site methods">
<!---
  Service:  SiteService
  Package:   com/oreilly/frdg/ScriptRepository
  Description: General utility methods for the site
--->
  <cffunction name="about" displayName="About"
  hint="Short paragraph and info about the company" access="remote"
  returnType="query" output="false">
  <!---
    Method: about
    Version: 1.0.0
    Author: Tom Muck
    Arguments:
      none
    Return: a query object with the information about the site
    Description:
      This service sends the information about the site back to the caller
  --->

    <!--- about bo


Part III: Advanced Flash Remoting