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.)
An event gateway can implement any of the following constructors:
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); }
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:
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; }
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 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.
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 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; }
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(); }
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:
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:
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.
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); } }
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:
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 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.
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); }