Inside Coldfusion MX

In this section, we are going to take a look at our first ColdFusion component and call it from a ColdFusion template. ColdFusion components work like regular ColdFusion templates; you can create component files in your webroot directory or any directory under it. Unlike ColdFusion pages, you save component files with the .cfc extension, such as componentName.cfc. In addition, all ColdFusion variable scopes are available to components, including session, client, server, and application.

You use the CFCOMPONENT and CFFUNCTION tags to create ColdFusion components. By itself, the CFCOMPONENT tag does not provide functionality; rather, it provides an envelope that describes the functionality you build in CMFL and enclose in CFFUNCTION tags. Furthermore, ColdFusion components provide some behaviors that are totally new and unique to CFCs and ColdFusion MX, such as web service deployment, description, and communication via SOAP.

Let's now create a new file in ColdFusion Studio called examplequery.cfc. Enter the code in Listing 6.1 into your editor and save it to your working directory.

Listing 6.1 testqrycfc.cfc

<cfcomponent> <cffunction name="getEmp" returnType="query"> <cfquery name="empQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.EmailName, Employees.WorkPhone, Employees.Extension FROM Employees </cfquery> <cfreturn #empQuery#> </cffunction> <cffunction name="getStartDate" returnType="query"> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees ORDER BY DateStarted ASC </cfquery> <cfreturn #deptQuery#> </cffunction> </cfcomponent>

At first this code might seem a little odd, but what we have done is create our first component with two methods or cffunctions. If you look, you will see that the code starts and ends with <cfcomponent></cfcomponent>, and nested inside it are our method calls wrapped inside <cffunction> </cffunction> tags, respectively.

Next you will notice that inside the CFFUNCTION tags are rather straightforward queries that perform some operations against our ICF database.

Note

Any code outside a CFFUNCTION tag will execute (as well as outside a CFCOMPONENT tag) and unexpected behavior might occur; so in general, you should never have code outside your CFFUNCTION or your CFFUNCTION tag.

If you have a Java or object-oriented background, you can compare it to a very simple Java class, which in general takes the following form:

Public class exampleclass { Public static void main (string[] args) { System.out.println ln("example"); } }

If you think about it, this simple class has a class wrapper where you define the name of the class and within that wrapper a main method. CFC also has a component wrapper, which holds inside of it various methods.

In our CFC, we have defined two methods that we can call getEmp and getStartDate both of which return query objects back to a client.

At this point, before we go further into the whys and wherefores of CFCs, let's actually call or instantiate this CFC.

Open your editor and create a file called testcfc.cfm like the one shown in Listing 6.2.

Listing 6.2 testcfc.cfm

<cfinvoke component="testqrycfc" method="getEmp" returnVariable="empResult"> <cfdump var=#empResult#> <cfinvoke component="testqrycfc" method="getDept" returnVariable="empResult"> <cfdump var=#empResult#>

Save this file and put it into your work directory. Then, using your web browser, call this template and see what the results are. They should look like those in Figure 6.1.

Figure 6.1. Output from testqrycfc.cfc.

This template enables us to "invoke" our CFC via the tag CFINVOKE, and it "knows" which CFC to call by providing the name of the component (component="testqrycfc") and the method we want to call by (method="getEmp"). Finally, we then define a return variable and perform a cfdump operation so that we can see the output via the collection returned by our CFC.

Now, before we talk too much about how to invoke CFCs, let's go a little deeper into how to create CFCs.

Creating CFCs

Although we have touched on the very basics of CFCs, we need to cover a lot of information before you can effectively start developing them. One of the first things we need to reaffirm is that CFCs enable you to use all CFML tags, functions, variables, and constructs as well as all variable and session scopes. CFCs are best used to abstract or objectify complex code and therefore can contain complex and lengthy code inside their CFFUNCTION tags or methods.

In such a case, you can easily use cfinclude to make a more manageable and readable CFC in which each lengthy method's code is included. In Listing 6.3, we are going to create a simple CFC that will help us manage our Employees database.

Listing 6.3 ums.cfc

<cfcomponent> <cffunction name="createEmp" returnType="query"> <cfquery name="createQuery" datasource="ICF" dbtype="ODBC"> (FirstName,LastName, Title, EmailName, Extension, WorkPhone, Status, Department, DateStarted) Values ( '#arguments.FirstName#', '#arguments.lastName#', '#arguments.Title#', '#arguments.EmailName#', '#arguments.Extension#', '#arguments.WorkPhone#', #arguments.Status#, #arguments.Department#, #arguments.DateStarted#) </cfquery> <cfreturn #createQuery#> </cffunction> <cffunction name="getEmp" returnType="query"> <cfquery name="getQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees ORDER BY DateStarted ASC </cfquery> <cfreturn #getQuery#> </cffunction> <cffunction name="deleteEmp" returnType="query"> <cfquery name="deleteQuery" datasource="ICF" dbtype="ODBC"> UPDATE Employees SET Status = 'inactive' Where EmployeeID = #arguments.EmployeeID# </cfquery> <cfreturn #deleteQuery#> </cffunction> <cfunction name="updateEmp" returnType="Query"> <cfquery name="updateQuery" datasource="ICF" dbtype="ODBC"> UPDATE Employees SET FirstName= '#arguments.FirstName#',LastName = '#arguments.lastName#', Title = '#arguments.Title#', EmailName = '#arguments.EmailName#', Extension = '#arguments.Extension#', WorkPhone '#arguments.WorkPhone#', Status #arguments.Status#, Department #arguments.Department#, DateStarted #arguments.DateStarted# Where EmployeeID = #arguments.EmployeeID# </cfquery> <cfreturn #updateQuery#> </cffunction> </cfcomponent>

As you can see, this CFC is rather lengthy, which is not uncommon when developing ColdFusion templates that perform complex tasks. In this case, our CFC encapsulates four functions, so it needs to be long; but lengthy templates make for code that is hard to understand and to debug, support, or enhance later. We need to be able to further abstract the actual working code from our functions.

To do this, we are going to make our code easier to read and make each functional block easier to manage. For example, let's remove the CFQUERY blocks and put them into included files that will be named after their function. The createQuery block becomes createEmp.cfm (see Listing 6.4).

Listing 6.4 ums1.cfc

<cfcomponent> <cffunction name="createEmp" returnType="query"> <cfinclude template="createEmp.cfm"> <cfreturn #createQuery#> </cffunction> <cffunction name="getEmp" returnType="query"> <cfinclude template="getEmp.cfm"> <cfreturn #getQuery#> </cffunction> <cffunction name="deleteEmp" returnType="query"> <cfinclude template="deleteEmp.cfm"> <cfreturn #deleteQuery#> </cffunction> <cfunction name="updateEmp" returnType="Query"> <cfinclude template="updateEmp.cfm"> <cfreturn #updateQuery#> </cffunction> </cfcomponent>

As you can see, this provides a much cleaner presentation and enables you to abstract much of the functional code from the CFC, allowing easier maintenance and testing of queries before you make them part of the CFC. For example, you can test all of your queries as they are before integrating them into a complex CFC. This enables you to do less testing on your overall CFC and during debugging. You can concentrate on issues with the CFC structure and code itself and not on the query, which you know already functions correctly.

Now let's look at how to actually use or work with CFCs that we create.

CFC Interaction

One thing you've surely noticed by now is that we have not really discussed how to pass values to and from CFCs. CFCs enable you to pass simple or very complex data in and out of them using everything from the CFINVOKE tag to URL variables, form variables, the Flash Gateway, and ColdFusion's new web services functionality.

Let's look at the different ways in which we can invoke a CFC's methods, first using the CFINVOKE tag in a simple ColdFusion template.

Using CFINVOKE

The CFINVOKE tag enables you to invoke the methods or functions from within ColdFusion pages or from within other components of the general form:

<cfinvoke component = "component name or reference" returnVariable = "variable name" argumentCollection = "argument collection" > <cfinvoke method = "method name" returnVariable = "variable name" argumentCollection = "argument collection" >

Note the difference in the two forms. Although in previous examples we called both the component and the method for a specific CFC, you actually do not need to call the component argument if the CFC has already been instated. The other two arguments are optional and are needed only if you expect a return variable or need to pass a structure of arguments.

As we have already seen, invoking CFCs in this manner from a ColdFusion page is a straightforward operation. Let's look at how we can invoke a CFC using CFOBJECT and how you can first instantiate a CFC and then invoke the method.

First we use the CFOBJECT tag to instantiate the component and create a pointer to the component using a variable name. For example:

<cfobject name="variable name" component="the name of the CFC">

To invoke component methods, use the cfinvoke tag. The cfinvoke tag's name attribute references the variable name in the cfobject tag's name attribute. Here is an example:

<cfobject name="emptest" component="exmpqry1"> <cfinvoke component=exmpqry1 method="getEmp">

The result of running this code is shown Figure 6.2.

Figure 6.2. Calling a CFC using CFOBJECT.

The question then comes to mind, why would you want to first instantiate the CFC before you invoke it? Well, if you just use CFINVOKE in a template, then every time you use CFINVOKE CFMX creates a new CFC, processes your request, and then destroys your CFC. By instantiating the CFC using CFOBJECT, your CFC is created and each call using CFINVOKE from the same template does not create a new CFC, thus increasing your performance. This is especially useful when you are doing many interactions with a CFC or when you have few but complicated interactions with your CFC, and it should be used as a best practice in situations in which performance is necessary. Another time to use CFOBJECT to instantiate your CFCs is when you want to preserve data stored in the component in the same instance between different method calls.

Interacting with CFC Using Forms

Now let's look at interacting with a CFC via a form. In this next example, we are going to ask you to modify the testqrycfc.cfc file again to look like Listing 6.5.

Listing 6.5 exmpqury1.cfc

<cfcomponent> <cffunction name="getEmp" returnType="query" access="remote"> <cfquery name="empQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.EmailName, Employees.WorkPhone, Employees.Extension FROM Employees </cfquery> <cfdump var=#empQuery#> </cffunction> <cffunction name="getStartDate" returnType="query"> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC"> SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees ORDER BY DateStarted ASC </cfquery> <cfreturn #deptQuery#> </cffunction> <cffunction name="getEmpName" returnType="query" access="remote"> <cfargument name="lastName" required="true"> <cfquery name="deptQuery" datasource="ICF" dbtype="ODBC" > SELECT Employees.FirstName, Employees.LastName, Employees.Title, Employees.DateStarted, Employees.Department FROM Employees WHERE LASTNAME LIKE '#arguments.lastName#' </cfquery> <cfreturn #deptQuery#> </cffunction> <cffunction name="createEmp" returnType="query" access="remote"> <!--- cf arguments are needed to recive arguments as well as they act as a first line of validation ---> <cfargument name="FirstName" required="true"> <cfargument name="LastName" required="true"> <cfargument name="Title" required="false"> <cfargument name="EmailName" required="false"> <cfargument name="Extension" required="false"> <cfargument name="WorkPhone" required="false"> <cfargument name="Status" required="true"> <cfargument name="Department" required="true"> <cfargument name="DateStarted" required="true"> <!--- start insert query ---> <cfquery name="empCreate" datasource="ICF" dbtype="ODBC" > Insert INTO Employees (FirstName,LastName, Title, EmailName, Extension, WorkPhone, Status, Department, DateStarted) Values ( '#arguments.FirstName#', '#arguments.lastName#', '#arguments.Title#', '#arguments.EmailName#', '#arguments.Extension#', '#arguments.WorkPhone#', #arguments.Status#, #arguments.Department#, #arguments.DateStarted#) </cfquery> <cfoutput>Record was added</cfoutput> </cffunction> </cfcomponent>

If you look at Listing 6.5, you will notice a few things that are different from the other CFFUNCTION blocks. The first is the different syntax in the CFFUNCTION block. We have added an attribute called access. For now, we are not going to get into this in depth, but cffunction has numerous attributes that enable you to define various things such as whether your CFC is public, whether it's remotely accessible, whether its security settings are based on specific roles, and other options. We will look at this in more depth in the section "Advanced ColdFusion Component Development" later in this chapter.

The next thing you will notice is a new tag called CFARGUMENT, which takes the following form:

<cfargument name="..." type="..." required="..." default="..." >

Table 6.1 provides the various descriptions and settings for each of the possible attributes of CFARGUMENT.

Table 6.1. cfargument Attributes and Descriptions

Attribute

Required

Type

Description

name

Required

String

The argument's name.

type

Optional

Data type

The data type of the argument. Used for validating data types.

required

Optional

Boolean

Used to specify whether a parameter is required to execute a component. Values can be either true or false.

default

Optional

All types

Provides a default value when no parameter is passed.

The CFARGUMENT tag enables you to define the parameters you want to pass to a CFC within a specific component method or within a specific CFFUNCTION block. As in the preceding example, if you want to pass multiple parameters to a CFC you need to define them using multiple CFARGUMENT tags. When you want to call an argument, you call it using the standard dot notation as if you were accessing information from a ColdFusion structure.

You can also use the CFARGUMENT tag much like CFPARAM, in that you can define whether a CFARGUMENT is required or not by using the required attribute and whether it should have a default value by using the default attribute. You can use these attributes to make more robust CFCs.

Now let's actually call this CFC from a form (see Listing 6.6).

Listing 6.6 cfcformpost.cfm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Untitled</title> </head> <cfquery name="getDepartments" datasource="ICF" dbtype="ODBC" > SELECT Department.DepartmentID, Department.DepartmentName FROM Department </cfquery> <body> <form action="exmpqry1.cfc" method="post"> <table border="0" cellspaceing="1"> <tr> <td>First Name</td> <td><input type="text" name="firstname" size="20"></td> </tr> <tr> <td>Last Name</td> <td><input type="text" name="lastname" size="20"></td> </tr> <tr> <td>Title</td> <td><input type="text" name="Title" size="20"></td> </tr> <tr> <td>Email Prefix</td> <td><input type="text" name="EmailName" size="20"></td> </tr> <tr> <td>Extension</td> <td><input type="text" name="Extension" size="20"></td> </tr> <tr> <td>WorkPhone</td> <td><input type="text" name="WorkPhone" size="20"></td> </tr> <tr> <td>Status</td> <td><input type="text" name="Status" size="20"></td> </tr> <tr> <td>Department</td> <td><select name="department"> <cfoutput query="getDepartments"><option value="#DepartmentID#">#DepartmentName#</option></cfoutput> </td> </tr> <tr> <td>Date Started</td> <td><input type="text" name="DateStarted" size="20"></td> </tr> <input type="Hidden" name="method" value="createEmp"> <tr> <td><input type="submit" value="Create Emp"></td> </tr> </table> </form> </body> </html>

When you look at this code, it looks very straightforward with the minor exception of two lines. One is the following form line:

<form action="exmpqry1.cfc" method="post">

This actually invokes our CFC and passes the information via an HTTP POST method. The second one is this hidden form:

<input type="Hidden" name="method" value="createEmp">

It passes to the CFC the specific method we want to call. When accessing CFC methods via forms, you need to make sure to pass along a form variable named method whose value is the method you want to call.

Save these files and point your browser to the form (refer to Listing 6.6), fill it out, and click submit. You should get something like Figure 6.3 in your browser.

Figure 6.3. An example of calling a CFC from a form.

Now that we understand how to use forms to work with CFCs, let's look at using URLs.

CFCs Using URLs

To pass parameters to component methods using a URL, you simply need to append the parameters you want to pass to the URL in standard URL query-string, name-value-pair syntax. For example, if you wanted call the getEmpName method on our CFC, you would need to pass the method and last name of an employee. If you wanted to pass it from a hyperlink, it would look like this:

<a href="exmpqry1.cfc?method=getEmpName&lastname=hahn">

To pass multiple parameters using a URL, all you need to do is delimit your name-value pairs using the ampersand (&) character. Here is an example:

<a href="exmpqry1.cfc?method=getEmpName&lastname=hahn&firstname=dan">

Also as with all parameter-passing techniques that use HTTP to communicate with a CFC, you need to make the specific methods you want to communicate with accessible by adding the remote clause to the ACCESS attribute in the CFFUNCTION tag.

Using CFScript with CFCs

CFScript has become a very popular method for developing ColdFusion applications, and it is very useful when developing large blocks of logic, setting many variables, or creating UDFs. At times, it will be important to be able to invoke your CFCs from inside a UDF or even a standard CFScript block.

To invoke a CFC's method using CFScript you will need to use the createObject function. After you create a component object, you use normal function syntax to invoke component methods. Listing 16.7 provides an example.

Listing 6.7 cfcscript.cfm

<b>CFC with CFSCRIPT:</b> <cfscript> exmpqrycfc=createObject("component"," exmpqry1"); exmpqrycfc.getEmp(); </cfscript> <br>

In this example, we have a single CFSCRIPT block that assigns the exmpqrycfc variable to the exmpqry1 component using the createObject function. We then call the CFC's method as a cfscript function. That's all there is to it.

Using Component Using the Flash Gateway

One of the great new features of ColdFusion MX is the capability for developers to call ColdFusion functions and indeed other types of objects (such as Java objects) through the Flash MX gateway. Until now, developers had a more cumbersome approach to creating dynamic Flash applications, but Flash MX enables you to seamlessly work with programming logic that lies outside of Flash and might indeed reside on a remote server or system.

Flash MX enables you to call CFCs specifically through the Flash gateway, which then brokers the exchange and accepts a response that it then passes on to the requesting Flash client, which can then work with the information. For a basic overview, see Figure 6.4.

Figure 6.4. A Flash object invoking a ColdFusion component via the Flash gateway.

This allows for some very exciting development possibilities, not only being able to create data-driven Flash applications much more simply and quickly, but also allowing developers to abstract functionality away from Flash and let it reside in CFCs. If a Flash developer knows the necessary methods and parameters to pass and call into a CFC, he can independently develop his UI widgets and Flash applications without ever needing to talk with server-side developers. As we will see in the following examples, ColdFusion now enables you to create a package-like notation for creating namespaces in your CFCs. This allows code reuse and gets rid of the possibility of namespace collision or, simply put, accidentally calling objects that have the same name but different functionality.

Let's look at a simple example. In Listing 6.8, we are going to create a simple CFC that, when called by our Flash object, returns a simple message that's contained inside a ColdFusion structure.

Note

Before you begin, you must have Flash MX and the NetServices/Salsa Flash MX add-on installed; otherwise, the examples will not work.

Listing 6.8 ObjectTest.cfc

<cfcomponent name="ObjectTest"> <cffunction name="getObject" access="remote"> <cfargument name="foo" required="true" /> <cfargument name="message" required="false" /> <cfset outStruct = StructNew() /> <cfset outStruct.foo = arguments.foo /> <cfset outStruct.message = arguments.message /> <cfset outStruct.from = "CF Component" /> <cfreturn outStruct /> </cffunction> </cfcomponent>

As you can see, this is a straightforward CFC like the others we have created in this chapter. In fact, there is nothing Flash-specific about it, and you could call or invoke it with any other methods defined earlier. So now let's create our Flash script that will call the CFC.

First you will need to open your Flash MX development environment and type the code in Listing 6.9 into the ActionScript window.

Listing 6.9 testnetservice.swf

#include "NetServices.as" #include "NetDebug.as" Result = function() {} Result.prototype.onResult = function(result) { trace("data received from gateway"); trace("result.foo : " + result.foo); trace("result.message : " + result.message); trace("result.from : " + result.from); } Result.prototype.onStatus = function(error) { trace("An Error occured!"); trace("Error : " + error.description); } var o = new Object(); o.foo = "bar"; o.message = "This orginated from Flash"; NetServices.setDefaultGatewayUrl("http://localhost:8100/flashservices/gateway"); var gw = NetServices.createGatewayConnection(); var server = gw.getService("cf6.ObjectTest", new Result()); server.getObject(o);

The first thing you will notice is that the ActionScript includes two libraries that are new to Flash MX. The first is called NetServices.as and the other is NetDebug.as.

These ActionScript libraries are required for you to take advantage of the new gateway services paradigm in Flash MX, and you will also use them to create your web service objects and to debug any problems you have with the Flash remoting service.

Next we create a new ActionScript class/object called Result. We will use the methods in the object to catch and manipulate the data received from the server.

Result.prototype.onResult = function(result) { trace("data received from gateway"); trace("result.foo : " + result.foo); trace("result.message : " + result.message); trace("result.from : " + result.from); }

Next we create a new function that will capture the structure returned from our CFC and output the contents of the structure. In actuality, you would want to create a loop that stepped through your structure, but here we have a very simple structure with known content, so we will just hard-code it.

In this instance, we capture data from the server when it is passed to Flash using onResult. To print the response from the server to the Flash output window, we use the function trace().

Now we need to add some debugging, which we do with this code block:

Result.prototype.onStatus = function(error) { trace("An Error occured!"); trace("Error : " + error.description); }

If for some reason our object has an error, this method will output the error so that we can debug our object.

Now we need to actually send something to our CFC. We can do that by creating a Flash object that we will send to our ObjectTest.cfc.

var o = new Object(); o.foo = "bar"; o.message = "This orginated from Flash";

Finally, we need to actually request the CFC. We can do it like this:

NetServices.setDefaultGatewayUrl("http://localhost:8100/flashservices/gateway"); var gw = NetServices.createGatewayConnection(); var server = gw.getService("thepathwhere.yousaved.yourcfc.ObjectTest", new Result()); server.getObject(o);

This code block is where we actually connect to the Flash gateway, and if you are developing on your local machine, the URL should be your local host, but it could be any URL to a Flash gateway.

The gateway will then return an object that represents a connection to the gateway. We use this connection to actually call our component (CFC) using the following:

var server = gw.getService("thepathwhere.yousaved.yourcfc.ObjectTest", new Result());

The path to which you save your CFC is referred to in dot notation instead of standard file path or URI notation. Thus, if you saved your CFC to www-root/coldfusionMX/chapter22/ObjectTest.CFC, it will become the following:

var server = gw.getService("coldfusionmx.chapter22.ObjectTest", new Result());

This dot notation allows your gateway connection to call the correct component on your targeted server, and by using unique names such as IPs, email addresses, and so on among your path, you will guarantee that you never have name collisions in your objects.

The preceding code now can call your CFC and store it in a variable called server. Next we call a method from this component using the following code:

server.getObject(o);

This also makes sure to pass our string object to the CFC's method that we invoked!

Now let's test what we have created. Go to Flash MX and use the Debug movie window. Press the Play icon and note the results in the pop-up window. You should see something like Figure 6.5.

Figure 6.5. Output from our CFC via the Flash gateway!

Retrieving CFC Output

So far, we have looked at various ways of invoking and instantiating CFCs and passing parameters to them, but only to a limited extent have we looked at how to output information from CFCs or request results from a CFC operation. So far, we have ignored this in our examples and have focused on how to build CFC functions/methods and pass information to them. Now we will look at how to retrieve results from CFCs.

In general, we will return our output results from a CFC by using the CFRETRUN tag. The CFRETURN tag takes the following form:

<CFRETURN Variable name >

CFRETURN only allows you to return one variable, so if you need to pass back complex information, you need to first create a structure of name-value pairs and return the structure to the calling template. In our examples, we are already returning a variety of data to the client using CFRETURN to not only return simple values but actual recordsets. You can return any data to the client as long as you can return it as a single variable.

In our examples, you can see that when we invoke the CFC, we also assign the return value to a temporary variable.

<cfinvoke component="examplequery" method="getEmp" returnVariable="empResult">

We then use cfdump to dynamically output the variable, which is a recordset, to the screen. We could just as easily have used CFLOOP to step through the recordset and output the information we wanted.

Also we can let the CFC just output information back to the client with CFOUTPUT. Usually you want to do this when you want to display a fixed message such as an error or some specific flag; but to pass back information to a calling template, you must use the CFRETURN tag.

Summary

We have now covered the basics of CFCs, including how to create them and how to invoke and instantiate them from forms. We also covered their direct use through CFINVOKE and using them through URLs and the Flash gateway. In the rest of the chapter, we will look at more advanced features of ColdFusion component models, such as roles-based security and access control.

Категории