Catch and Throw

[Previous] [Next]

You can use catch and throw to manage the execution flow in your OmniMark programs. It is probably easiest to think of catch and throw as an exception-handling mechanism. What is an exception? An exception is essentially an interruption to the normal flow of program processing. Some exceptions come whether you want them or not, in the form of run-time program errors or failure to communicate with the world outside your program. Other exceptions are used to solve programming problems.

A simple piece of code can usually handle 90 percent of all exceptions. The other 10 percent are exceptions that require significantly different processing. Dividing a problem up into normal cases and exceptions is a convenient way to simplify and organize your code. This does not mean that the exceptions are errors or even unexpected. Often the exceptional cases are what you are most interested in. Programming with exceptions is simply a technique for designing an algorithm to solve a particular problem.

You can use catch and throw in a program that nests deeply when you want to quickly get out of the nesting. OmniMark does not have a "goto" verb (thankfully), so getting out of a nested loop or other hierarchical area can be kind of tricky. With catch and throw, all you need to do is create a catch at the destination point and then throw to that point from somewhere inside the nested area. OmniMark is usually smart enough to take care of whatever housekeeping is required to exit the loop.

Catching Program Errors

The simplest use of catch and throw allows your program to recover when something goes wrong. Consider the following code, which contains the server loop of a simple server program:

 include "omioprot.xin" include "omtcp.xin" process local TCPService my-service set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response set my-connection to TCPServiceAcceptConnection my-service open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done again 

Any error in the program or with the TCP connection will cause this program to terminate, since nothing exists to handle the error. You should always write a server program to stay running if at all possible, so we need to add something to allow the program to recover:

 include "omioprot.xin" include "omtcp.xin" process local TCPService my-service set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response set my-connection to TCPServiceAcceptConnection my-service open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error again 

We have added only a single line, but this version of the program is much more robust. If any error occurs while in the repeat loop of any of the find rules invoked by submit, execution will transfer to the line catch #program-error. Along the way, OmniMark will clean up after itself. Local scopes will be terminated and resources released.

No attempt is made to salvage the work that was in progress when the error happened. That work and all its associated resources are thrown away. But the server will stay up and running, ready to receive the next request.

Of course, we'd like to know that the error occurred and why it occurred. The constant #program-error makes information available so that we can act on the error or at least report it. To ensure that errors get logged, we can rewrite the program like this:

 include "builtins.xin" include "omioprot.xin" include "omtcp.xin" process local TCPService my-service set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response set my-connection to TCPServiceAcceptConnection my-service open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error code c message m location l log-message ( "Error " || "d" format c || " " || m || " at " || l || " time " || date "=Y/=M/=D =h:=m:=s" ) again 

Notice that the catch line prevents everything that follows from being executed. The code inside a catch block is run only if the program encounters an error.

So far, we have seen nothing of the "throw" part of catch and throw. This is because OmniMark itself throws to #program-error. OmniMark can also throw to #external-exception if it has a problem communicating with the external world (such as being unable to open a file or communicate successfully with an OMX component). We didn't bother to catch #external-exception in the preceding code because the failure of a program to catch an external exception is itself a program error, which causes a throw to #program-error. In other circumstances we might want to use #external-exception explicitly. Suppose one of the find rules in our server program tried and failed to open a file:

 find "open file " letter+ => fowl output file fowl catch #external-exception output "Unable to open file " || fowl || "." 

The attempt to open the named file fails. This causes an external exception at the first output action. We catch the exception and provide alternate output. The program then continues as if nothing had happened.

Now our server is more robust since an error opening a file will not abort the processing of the current request. Instead, the client will receive the error message we output along with any other information the request generates.

Exception Handling

When it comes to throwing things, OmniMark does not get to have all the fun. You can create your own exceptions, throw your own throws, and define your own catches. The following program uses a programmer-defined exception to shut down the server by remote control:

 include "builtins.xin" include "omioprot.xin" include "omtcp.xin" declare catch nap-time process local TCPService my-service set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response set my-connection to TCPServiceAcceptConnection my-service open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error again catch nap-time find "sleep" throw nap-time 

Here we have a classic case of an exception. Every request to our server is a request for information—every request except one. The "sleep" request is an instruction to the server to shut itself down. This is the exceptional case and we handle it with an exception.

In the exceptional case in which we shut down the server, we need to jump out of the connection loop. We do this with a catch outside the loop. The catch is named nap-time. Catches are named so that we can have more than one and have each one catch something different. Like all names introduced into an OmniMark program, the name of a catch must be declared, which we do in the first line of the program. We place the catch outside the request-handling loop. Because OmniMark cleans up after itself while performing a throw, all resources belonging to the local scope inside the loop are properly closed down. Then, since we are outside the loop and at the end of the process rule, the program simply ends, shutting down the server.

The throw itself is simple. Having detected the exceptional case (the sleep request), we simply throw to the appropriate catch by name. OmniMark handles everything else.

Throwing Additional Information

When OmniMark throws to #program-error or #external-exception, it adds information in the form of three parameters: code (identity for #external exception), message, and location. You can also pass additional information with your throws by adding parameters to your catch declaration, following the form of a function definition. The following program adds the capability to log the reason for putting a server to sleep:

 include "builtins.xin" include "omioprot.xin" include "omtcp.xin" declare catch nap-time because value stream reason process local TCPService my-service set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response set my-connection to TCPServiceAcceptConnection my-service open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error again catch nap-time because r log-message ( "Shut down because " || r || " Time: " || date "=Y/=M/=D =h:=m:=s" ) find "sleep" white-space* any* => the-reason throw nap-time because the-reason 

The find rule captures the rest of the "sleep" message and uses it as a parameter to the throw. The catch receives the data and uses it to create the appropriate log message.

Cleaning Up After Yourself

I said that OmniMark cleans up after itself, and it does: it cleans up everything it knows about. But this can still leave you with cleanup of your own to do. Or there might simply be statements that you always want to execute, even if an exception occurs. For this, OmniMark provides the always keyword. Let's suppose that our server does its own connection logging and uses OmniMark's logging facility for errors. We want the connection log file closed between requests to make it easy to cycle the log files, so we open and close the log file for each connection:

 declare catch nap-time because value stream reason include "builtins.xin" include "omioprot.xin" include "omtcp.xin" global stream log-file-name initial {"nap-time.log"} process local TCPService my-service local stream my-log set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response reopen my-log as file log-file-name set my-connection to TCPServiceAcceptConnection my-service put my-log TCPConnectionGetPeerIP my-connection || date "=Y/=M/=D =h:=m:=s" open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done close my-log catch #program-error code c message m location l log-message ( "Error " || "d" format c || " " || m || " at " || l || " time " || date "=Y/=M/=D =h:=m:=s" ) again catch nap-time because reason log-message ( "Shut down because " || reason || " Time: " || date "=Y/=M/=D =h:=m:=s" ) 

In this code, a problem processing the request would cause a throw to #program-error. This would mean that the line close my-log was never executed and the log file would remain open. (This is an exception OmniMark can't clean up itself, since the stream my-log belongs to a wider scope that is not being closed.) We want the line close my-log to be executed always, whether there is an error or not. To ensure this, we use always:

 declare catch nap-time because value stream reason include "builtins.xin" include "omioprot.xin" include "omtcp.xin" global stream log-file-name initial {"nap-time.log"} process local TCPService my-service local stream my-log set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response reopen my-log as file log-file-name set my-connection to TCPServiceAcceptConnection my-service put my-log TCPConnectionGetPeerIP my-connection || date "=Y/=M/=D =h:=m:=s" open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error code c message m location l log-message ( "Error " || "d" format c || " " || m || " at " || l || " time " || date "=Y/=M/=D =h:=m:=s" ) always close my-log again catch nap-time because reason log-message ( "Shut down because " || reason || " Time: " || date "=Y/=M/=D =h:=m:=s" ) find "sleep" white-space* any* => the-reason throw nap-time because the-reason 

When a throw happens, OmniMark closes scopes one by one until it finds a scope that contains a catch for that throw. As it does so, OmniMark executes any code in an always block in each of those scopes, including the scope that contains the catch. So in this example, the close line will execute before the catch block is executed, no matter where or why an error occurs.

Programming with exceptions is a powerful technique that can make your programs both more reliable and easier to read and write. Here is our full server program with all our catch and throw functionality, plus logging of our external exception (but minus the find rules that do the rest of the work):

 declare catch nap-time because value stream reason include "builtins.xin" include "omioprot.xin" include "omtcp.xin" global stream log-file-name initial {"nap-time.log"} process local TCPService my-service local stream my-log set my-service to TCPServiceOpen at 5432 repeat local TCPConnection my-connection local stream my-response reopen my-log as file log-file-name set my-connection to TCPServiceAcceptConnection my-service put my-log TCPConnectionGetPeerIP my-connection || date "=Y/=M/=D =h:=m:=s" open my-response as TCPConnectionGetOutput my-connection using output as my-response do submit TCPConnectionGetLine my-connection done catch #program-error code c message m location l log-message ( "Error " || "d" format c || " " || m || " at " || l || " time " || date "=Y/=M/=D =h:=m:=s" ) always close my-log again catch nap-time because reason log-message ( "Shut down because " || reason || " Time: " || date "=Y/=M/=D =h:=m:=s" ) find "sleep" white-space* any* => the-reason throw nap-time because the-reason find "open file " letter+ => foo output file foo catch #external-exception identity i message m location l output "Unable to open file " || foo || "." log-message ( "Error " || i || " " || m || " at " || l || " time " || date "=Y/=M/=D =h:=m:=s" ) 



XML and SOAP Programming for BizTalk Servers
XML and SOAP Programming for BizTalk(TM) Servers (DV-MPS Programming)
ISBN: 0735611262
EAN: 2147483647
Year: 2000
Pages: 150

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net