Adobe ColdFusion 8

Building an event gateway

This section describes how to build an event gateway. To build a Gateway class, you can start with the EmptyGateway.java file as a template. (In the server configuration, this file is located in the cf_root/gateway/src/examples/ directory; in the J2EE configuration, the file is in the cf_root/WEB-INF/cfusion/gateway/src/examples/ directory.) This file defines a nonfunctional event gateway, but has the basic skeleton code for all Gateway class methods.

Wherever possible, the following sections use code based on the sample Socket event gateway to show how to implement event gateway features. (In the server configuration, this file is cf_root/gateway/src/examples/socket/SocketGateway.java; in the J2EE configuration, the file is cf_root/WEB-INF/cfusion/gateway/src/examples/socket/SocketGateway.java.)

Class constructor

An event gateway can implement any of the following constructors:

  • MyGateway(String gatewayID, String configurationFile)
  • MyGateway(String gatewayID)
  • MyGateway()

When ColdFusion starts, it calls the constructor for each event gateway instance that you configure in ColdFusion. (ColdFusion also calls the gateway Start method after the event gateway is instantiated.). ColdFusion first attempts to use the two-parameter constructor.

Because each event gateway instance must have a unique ID, ColdFusion provides redundant support for providing the ID. If the event gateway implements only the default constructor, ColdFusion provides the ID by calling the event gateway's setGatewayID method.

If the event gateway does not implement the two-parameter constructor, it does not get configuration file information from ColdFusion.

The constructor normally calls the static GatewayServices.getGatewayServices method to access ColdFusion event gateway services. Although you need not do this, it is a good coding practice.

A minimal constructor that takes only a gateway ID might look like the following:

public MyGateway(String gatewayID) {
    this.gatewayID = gatewayID;
    this.gatewayService = GatewayServices.getGatewayServices();
}

The gateway constructor must throw a coldfusion.server.ServiceRuntimeException exception if there is an error that otherwise cannot be handled. For example, you should throw this exception if the event gateway requires a configuration file and cannot read the file contents.

If your gateway uses a configuration file, the constructor should load the file, even if the Start method also loads the file. You should do this because the constructor does not run in an independent thread, and ColdFusion can display an error in the ColdFusion Administrator of the file fails to load. If the Start method, which does run in a separate thread, fails to load the file, ColdFusion logs the error, but it cannot provide immediate feedback in the administrator.

The sample Socket event gateway has a single constructor that takes two parameters. It tries to load a configuration file. If you specify a configuration file in the ColdFusion Administrator, or the file path is invalid, it gets an IO exception. It then uses the default port and logs a message indicating what it did. The following example shows the Gateway constructor code and the loadProperties method it uses:

public SocketGateway(String id, String configpath)
{
    gatewayID = id;
    gatewayService = GatewayServices.getGatewayServices();
    // log things to socket-gateway.log in the CF log directory
    log = gatewayService.getLogger("socket-gateway");
    propsFilePath=configpath;
    try
    {
        FileInputStream propsFile = new FileInputStream(propsFilePath);
        properties.load(propsFile);
        propsFile.close();
        this.loadProperties();
     }
     catch (IOException e)
    {
        // Use default value for port and log the status.
        log.warn("SocketGateway(" + gatewayID + ") Unable to read configuration 
            file '" + propsFilePath + "': " + e.toString() + ".Using default port
            " + port + ".", e);
    }
}

private void loadProperties() {
    String tmp = properties.getProperty("port");
    port = Integer.parseInt(tmp);
}

Providing Gateway class service and information routines

Several gateway methods perform event gateway configuration services and provide event gateway information. The ColdFusion event gateway services call many of these methods to configure the event gateway by using information stored by the ColdFusion Administrator, and to get access to resources and information that are needed by event gateway services and applications. Some of these methods can also be useful in event gateway code. The following methods provide these services and information:

  • setCFCListeners
  • setGatewayID
  • getHelper
  • getGatewayID
  • getStatus

ColdFusion calls the setCFCListeners method with the CFC or CFCs that are specified in the ColdFusion Administrator when it starts a gateway. ColdFusion also calls the method in a running event gateway when the configuration information changes, so the method must be written to handle such changes. The setCFCListeners method must save the listener information so that the gateway code that dispatches incoming messages to gateway services can use the listener CFCs in setCFCPath methods.

ColdFusion calls the setGatewayID method when it starts a gateway. The getGatewayID method must return the value set by this method.

ColdFusion calls the getHelper method when an application calls the CFML GetGatewayHelper function.

The following code shows how the SocketGateway class defines these methods. To create a new gateway, modify the getHelper definition to return the correct class, or to return null if there is no gateway helper class. Most gateways do not need to change the other method definitions.

public void setCFCListeners(String[] listeners) {
    this.listeners = listeners;
}
public GatewayHelper getHelper() {
    // SocketHelper class implements the GatewayHelper interface.
    return new SocketHelper();
}
public void setGatewayID(String id) {
    gatewayID = id;
}
public String getGatewayID() {
    return gatewayID;
}
public int getStatus() {
    return status;
}

Starting, stopping, and restarting the event gateway

Because an event gateway uses at least one listener thread, it must have start, stop, and restart methods to control the threads. These methods must also maintain the status variable that the Gateway class getStatus method checks, and change its value among STARTING, RUNNING, STOPPING, STOPPED, and FAILED, as appropriate.

The start method

The start method initializes the event gateway. It starts one or more listener threads that monitor the gateway's event source and respond to any messages it receives from the source.

The start method should return within a time-out period that you can configure for each event gateway type in the ColdFusion Administrator. If it does not, the ColdFusion Administrator has a Kill on Startup Timeout option for each gateway type. If you select the option, and a time-out occurs, the ColdFusion starter thread calls an interrupt on the gateway thread to try to kill it, and then exits.

Note: If the start method is the listener (for example, in a single-threaded gateway), the method does not return until the gateway stops. Do not set the Kill on Startup Timeout option in the ColdFusion Administrator for such gateways.

If the gateway uses a configuration file, the start method should load the configuration from the file. Doing so lets users change the configuration file and restart the gateway without restarting ColdFusion. Applications should also load the configuration file in the constructor; for more information, see Class constructor.

In the SocketGateway class, the start method starts an initial thread. (In a single-threaded Gateway, this would be the only thread.) When the thread starts, it calls a socketServer method, which uses the Java ServerSocket class to implement a multithreaded socket listener and message dispatcher. For more information on the listener, see Responding to incoming messages.

public void start()
{
    status = STARTING;
    listening=true;
    // Start up event generator thread
    Runnable r = new Runnable()
    {
        public void run()
        {
            socketServer();
        }
    };
    Thread t = new Thread(r);
    t.start();
    status = RUNNING;
}

The stop method

The stop method performs the event gateway shutdown tasks, including shutting down the listener thread or threads and releasing any resources. The following example shows the SocketGateway stop method:

public void stop()
{
    // Set the status variable to indicate that the server is stopping.
    status = STOPPING;
    // The listening variable is used as a switch to stop listener activity.
    listening=false;
    // Close the listener thread sockets.
    Enumeration e = socketRegistry.elements();
    while (e.hasMoreElements()) {
        try
        {
            ((SocketServerThread)e.nextElement()).socket.close();
        }
        catch (IOException e1)
        {
            // We don't care if a close failed.
            //log.error(e1);
        }
    }
    // Close and release the serverSocket instance that gets requests from the
    // network.
    if (serverSocket != null) {
        try
        {
            serverSocket.close();
        }
        catch (IOException e1)
        {
        }
        //Release the serverSocket.
        serverSocket = null;
    }
    // Shutdown succeeded; set the status variable.
    status = STOPPED;
}

The restart method

In most cases, you implement the restart method by calling the stop method and the start method consecutively, but you might be able to optimize this process for some services. The following code shows the SocketGateway class restart method:

public void restart() {
    stop();
    start();
}

Responding to incoming messages

One or more listener threads respond to incoming messages (events). The threads must include code to dispatch the messages to ColdFusion event gateway services, as follows:

  1. Create a CFEvent instance.
  2. Create a Map instance that contains the message and any other event gateway-specific information, and pass it to the CFEvent setData method.
  3. Call the CFEvent setOriginator method to specify the source of the message. (This is required if the ColdFusion application will send a response.)
  4. Call the CFEvent setGateWayType method to specify the event gateway type.
  5. Set any other CFEvent fields where the default behavior is not appropriate; for example, call the setCFCPath method to replace the default listener CFC. (For information on default CFEvent fields, see CFEvent class.)
  6. Call the gatewayService.addEvent method to dispatch the CFEvent instance to ColdFusion.
  7. Handle cases where the event is not added to the event gateway service queue (the addEvent method returns False).

If your application sends any messages to multiple listener CFCs, the gateway must create and configure a CFEvent instance and call the gatewayService.addEvent method to send the message to each separate listener CFC. The gateway's setCFCListeners method must make the CFC paths available to the gateway for configuring the CFEvent instances.

If your ColdFusion server carries a heavy event gateway message load, the ColdFusion event gateway services event queue might reach the maximum value set in the ColdFusion Administrator. When this happens, the gatewayService.addEvent method returns False and fails. Your code can do any of the following:

  • Return a message to the sender to indicate that their message was not received.
  • Wait until the queue is available by periodically comparing the values returned by the GatewayService getQueueSize and getMaxQueueSize methods, and retry the addEvent method when the queue size is less than the maximum.
  • Log the occurrence using the logger returned by the GatewayService getLogger method. (For more information, see Logging events and using log files.)

The SocketGateway class implements the listener using a java.net.ServerSocket class object and SocketServerThread listener threads. (See the SocketGateway source for the SocketServerThread code.) When the listener thread gets a message from the TCP/IP socket, it calls the following processInput method to dispatch the message to ColdFusion. This method explicitly sets all required and optional CFEvent fields and sends the event to ColdFusion. If the addEvent call fails, it logs the error.

Note: Much of the processInput method code supports multiple listener CFCs. A gateway that uses only a single listener cfc, would require only the code in the latter part of this method.

private void processInput(String theInput, String theKey)
{
    // Convert listeners list to a local array
    // Protect ourselves if the list changes while we are running
    String[] listeners;
    int size = cfcListeners.size();
    if (size > 0)
    {
        // Capture the listeners list
        synchronized (cfcListeners)
        {
            listeners = new String[size];
            cfcListeners.toArray(listeners);
        }
    }
    else
    {
        // Create a dummy list
        listeners = new String[1];
        listeners[0] = null;
    }
    // Broadcast to all the CFC listeners
    // Send one message at a time with different CFC address on them
    for (int i = 0; i < listeners.length; i++)
    {
        String path = listeners[i];
        CFEvent event = new CFEvent(gatewayID);
        Hashtable mydata = new Hashtable();
        mydata.put("MESSAGE", theInput);
        event.setData(mydata);
        event.setGatewayType("SocketGateway");
        event.setOriginatorID(theKey);
        event.setCfcMethod(cfcEntryPoint);
        event.setCfcTimeOut(10);
        if (path != null)
            event.setCfcPath(path);
            boolean sent = gatewayService.addEvent(event);
        if (!sent)
            log.error("SocketGateway(" + gatewayID + ") Unable to put message on
                vent queue. Message not sent from " + gatewayID + ", thread " + theKey
                + ".Message was " + theInput);
    }
}

Responding to a ColdFusion function or listener CFC

The ColdFusion event gateway services call the event gateway's outgoingMessage method to handle messages generated when an event gateway application listener CFC's listener method returns a message or any CFML code calls a SendGatewayMessage function. This method must send the message to the appropriate external resource.

The outgoingMessage method's parameter is a CFEvent instance, containing the information about the message to send out. The CFEvent getData method returns a Map object that contains event gateway-specific information about the message, including any message text. All CFEvent instances received by the outgoingMessage contain information in the Data and GatewayID fields.

CFEvent instances returned from listener CFC onIncomingMessage methods include the incoming message's originator ID and other information. However, a gateway that might handle messages from the ColdFusion SendGatewayMessage function cannot rely on this information being available, so it is good practice to require that all outgoing messages include the destination ID in the data Map.

The outgoingMessage method returns a String value. The CFML sendGatewayMessage function returns this value to the ColdFusion application. The returned string should indicate the status of the message. By convention, ColdFusion event gateway outgoingMessage methods return "OK" if they do not encounter errors and do not have additional information (such as a message ID) to return.

Because event messages are asynchronous, a positive return normally does not indicate that the message was successful delivered, only that the outgoingMessage method successfully handled the message. In some cases, however, it is possible to make the outgoingMessage method at least partially synchronous. The SMS gateway, for example, provides two outgoingMessage modes:

Asynchronous mode: The outgoingMessage method returns when the message is queued internally for delivery to the messaging provider's short message service center (SMSC)

Synchronous mode: The method does not return until the message is delivered to the SMSC, or an error occurs.

This way, an SMS application can get a message ID for later use, such as to compare with a message receipt.

Example outgoingMessage method

The following outgoingMessage method is similar to the version in the SocketGateway class. It does the following:

  1. Gets the contents of a MESSAGE field of the Data Map returned by the CFEvent class getData method.
  2. Gets the destination from an outDestID field in the data Map.
  3. Uses the destination's socket server thread to write the message.
    public String outgoingMessage(coldfusion.eventgateway.CFEvent cfmsg) {
        String retcode="ok";
        // Get the table of data returned from the event handler
        Map data = cfmsg.getData();
        String message = (String) data.get("MESSAGE");
        // Find the right socket to write to from the socketRegistry hashtable
        // and call the socket thread's writeoutput method.
        // (Get the destination ID from the data map.)
        if (data.get("outDestID") != null)
            ((SocketServerThread)socketRegistry.get(data.get("outDestID"))).
                    writeOutput(message);
        else {
            System.out.println("cannot send outgoing message. OriginatorID is not
                available.");
            retcode="failed";
            }
        return retcode;
    }
    

Logging events and using log files

The GatewayServices.getLogger method returns an instance of the coldfusion.eventgateway.Logger class that you can use to log messages to a file in the ColdFusion logs directory. (You set this directory on the ColdFusion Administrator Logging Settings page.) The method can take no parameter, or one parameter:

  • The default GatewayServices.getLogger method uses the eventgateway.log file.
  • Optionally, you can specify a log filename, without the .log extension or directory path.

The following example tells ColdFusion to log messages from the gateway to the mygateway.log file in the ColdFusion logs directory:

coldfusion.eventgateway.Logger log =getGatewayServices().getLogger("mygateway");

The Logger class has the following methods, all of which take a message string. The method you use determines severity level that is set in the log message.

  • info
  • warn
  • error
  • fatal

You can also pass these methods an exception instance as a second parameter. When you do this, ColdFusion puts the exception information in the exception.log file in the ColdFusion logs directory.

You can use these methods to log any messages that you find appropriate. If you use the default eventgateway.log file, however, remember that it is used by all ColdFusion standard gateways, and might be used by other gateways. As a result, you should limit the messages that you normally log to this file to infrequent normal occurrences (such as gateway startup and shutdown) or errors for which you want to retain data.

ColdFusion uses the following format for the message text, and your application should follow this pattern:

GatewayType (Gateway ID) Message

The SMS event gateway, for example, includes the following exception catching code, which logs a general exception messages and the exception name in the eventgateway.log file, and also (automatically) logs the exception in the exceptions.log file:

catch(Exception e)
{
    logger.error("SMSGateway (" + gatewayID + ") Exception while processing
    incoming event: " + e, e);
}

Note: When you are developing an event gateway application, you can use the ColdFusion Log viewer to inspect your log files and the standard ColdFusion log files, including the eventgateway.log file and exception.log file. You can use the viewer to filter the display, for example, by selecting different severity levels, or searching for keywords.