WARNING: This server provides a static reference view of the NetKernel documentation. Links to dynamic content do not work. For the best experience we recommend you install NetKernel and view the documentation in the live system .

A companion technology necessary for building robust and manageable system is system and application logging.

While evaluating a request, an exception may be raised within an endpoint's executing code, either due to a local processing error or a request processing error. The exception may be handled locally (within the endpoint), not caught locally and allowed to propagate back towards the kernel or caught, wrapped in an instance of NKFException and thrown.

Local Processing

An exception may be raised by code executing locally within an endpoint. In Java there are two broad categories of errors that can occur - java.lang.Error and java.lang.Exception, both of which are sub-classes of java.lang.Throwable.

Endpoint code may catch and handle java.lang.Exception and its sub-classes. However, endpoint code should never catch exceptions that are a sub-class of java.lang.Error nor an instance of java.lang.Throwable unless you know exactly what you are doing. Instead, these should be allowed to propagate back to the kernel because the kernel has special handlers for them and their presence can impact the state of the kernel.

Request Processing

An exception may be raised when an endpoint issues a request. If the endpoint resolved to handle the issued request throws an exception, its base class NKFEndpointImpl captures the exception and creates an exception representation which is returned in the response. The kernel passes the response back to the requesting endpoint and the requesting client endpoint's base classes unpack the exception representation and re-throw the exception in the requesting code.

This design provides language independence and clean integration with the ROC abstraction. Language independence means an exception may be thrown in any supported language, using its native approach, and be caught by a requesting endpoint written in any supported language, using its native approach. From an ROC perspective, an exception is simply a resource representation. Because an exception is a resource representation there are special caching considerations that are discussed below.

Local Exception Handling

An exception may be handled by an endpoint when it has sufficient information and context. For example, if an exception could occur during an internal operation and the proper handling is the use a default value, then the exception can be completely handled within the endpoint.

For example, the following code illustrates the use of a default value when a divide-by-zero exception occurs:

try
  {
  value = a / b;
  }
catch(DivideByZeroException e)
  {
  value = DEFAULT_VALUE;
  }

In a second example, an endpoint issues a request and handles the thrown NKFException locally:

request = context.createRequest("active:toUpper");
request.addArgumentByValue("operand", "Convert this to upper case.");
request.setRepresentation(String.class);
String representation;
try
  {
  representation = (String)context.issueRequest(request);
  }
catch(NKFException nkfe)
  {
  // Use the default value if the request fails for any reason
  representation = DEFAULT_VALUE;
  }

Propagate Exception

If an exception is not caught and handled locally it will propagate out of the endpoint method.

public void onSource(INKFRequestContext context) throws Exception
  {
  ...
  // Possible divide-by-zero exception
  int value = a / b;
  ...
  }

and in the case of a sub-request:

public void onSource(INKFRequestContext context) throws Exception
  {
  ...
  INKFRequest request = context.createRequest("active:toUpper");
  request.addArgumentByValue("operand", "Convert this to upper case");
  request.setRepresentation(String.class);
  String representation = (String)context.issueRequest(request);
  ...
  }

In most cases, the approach of allowing exception to propagate is both expedient and effective.

Throw NKFException

Endpoints in some NetKernel applications need fine-grained control over error handling and exception reporting. These endpoint will create, configure and throw an instance of NKFException, the base of the NetKernel exception handling infrastructure.

NetKernel uses a different philosophy about exceptions than most pure Java applications. Instead of using a hierarchy of class types to encode exception information, the single class NKFException is used with different exception IDs. An instance of NKFException can contain an exception ID, a human readable message, a location and an instance of Throwable to be wrapped as the root cause.

Developers have two choices regarding the exception ID. The ID may contain a string constant that is used as a look-up key for a human-readable ID in a localized messages.properies file or a human readable ID may be used directly. Refer to the section on localization for details about the first option. The exception message is a human readable message that describes the problem. The sub-class of java.lang.Throwable that is the root cause of the problem can also be included as a wrapped exception.

The class NKFException provides several constructors, as illustrated below.

// Use only exception ID
catch(Exception e)
  {
  throw new NKFException("Customer not added")
  }

// Use exception ID and message
catch(Exception e)
  {
  throw new NKFException("Customer not added", "The database is full.");
  }

// Use exception ID, message and wrap root cause
catch(Exception e)
  {
  throw new NKFException("Customer not added", "The database is full", e);
  }

The third option is highly recommended as it creates a chain of exceptions from the root cause to the highest level exception. This structure makes it possible to perform exception analysis in the upper levels of the application.

Exception Representation Caching

NetKernel attempts to cache resource representations. Sometimes an exception, returned through the kernel as a representation, should not be cached. For example, an exception that reports resource exhaustion should not be cached because a subsequent request could return successfully if allowed to be evaluated by its resolved endpoint.

By default, all exception representations are immediately expired (and therefore not cached). If an accessor knows that all exceptions it could throw are permanent, then it can change the default behavior for its exceptions by calling the declareUnhandledExceptionsExpired(...) method in its constructor. In this example, the endpoint MyEndoint is declaring that all exceptions it throws should not be immediately expired (and potentially cached):

public MyEndpoint()
 {
 declareUnhandledExceptionsExpired(false);
 }

If however you want certain exceptions rather than all of them to be cachable, you can catch those explicitly and create a response from them. This is common when exceptions due to processing or validation errors could be cached but errors due to IO would not be. For example:

void onSource(INKFRequestContext context) throws Exception
{
  try
  {
    //do work
  }
  catch (MyException e)
  { context.createResponseFrom(e);
  }
}

Exception Analysis

When a NKFException is caught it can be analyzed to determine information about the issue and the call-stack context. Methods are provided to retrieve the top-level exception information and the deepest exception information.

InformationTop LevelDeepestReturns
Exception IDgetID()getDeepestID()String
MessagegetMessage()getDeepestMessage()String
Root Cause of this exceptiongetCause()getDeepestCause()Throwable
Location (see below)getLocation()N/AString
Stack Trace (see below)getStackTrace()N/AStackTraceElement[]

The method getLocation() returns a String that contains information about the location where the exception was raised. This is provided to allow language runtime environments to information such as the line number where the exception was raised. The method getStackTrace() returns an array of StackTraceElement instances.

Localization

NetKernel supports localization of exception information. Please contact 1060 Research directly for information about internationalization support.

Semantic Exception Processing

Frequently applications are designed in multiple tiers or layers with each one focused on a particular level of abstraction. For example, an application might have an integration tier that abstracts the technology used and location of enterprise information. Next would be a domain tier where business resources and services are place and finally a transport / user interface tier adapts various mode of external interaction with the application.

If an exception occurs within the integration tier the semantics may be relevant locally, such as an exception thrown if a database become unavailable. At the next level the database connection issue may have specific meaning for the operation such as "Cannot add customer".

NetKernel provide a means to associate local semantic messages to exceptions as they flow upwards. An exception handler at the top level of an application might view the accumulated messages as a "semantic stack" that should be reported in an email, a log entry, etc. An example "semantic stack" could be:

Unable to place order
Cannot add customer
Database connection failed

A semantic message can be added to an exception using the method addAdditionalField(...) which takes two Strings, a name and a value. In the following code fragments you will see how to add semantic messages and then extract the semantic message stack.

This code shows how to add a semantic message to an exception:

catch(Exception e)
  {
  NKFException nkfe = new NKFException("ID", "message", e);
  nkfe.addAdditionalField("Semantic", "Database connection failed");
  throw nkfe;
  }

At the next level the following code adds the next level in the semantic stack:

catch(Exception e)
  {
  NKFException nkfe = new NKFException("ID", "message", e);
  nkfe.addAdditionalField("Semantic", "Cannot add customer");
  throw nkfe;
  }

and finally:

catch(Exception e)
  {
  NKFException nkfe = new NKFException("ID", "message", e);
  nkfe.addAdditionalField("Semantic", "Unable to place order");
  throw nkfe;
  }

At the top level, the following code catches the exception and extracts the semantic stack.

catch (NKFException nkfe)
  {
  // Convert the exception to an HDS representation
  IHDSNode node = context.transrept(nkfe, IHDSNode.class);

  // Gather all "additional fields" from the exception stack
  IHDSNodeList nodeList = node.getNodes("//field");

  // Process the set of "additional fields"
  for (int i = 0; i < nodeList.size(); i++)
    {
    IHDSNode hds = nodeList.get(i);
    String name = (String)hds.getFirstValue("name");
    String value = (String)hds.getFirstValue("value");
    // Report on the semantic stack. This example prints to the console
    System.out.println("name [" + name + "], value [" + value +"]");
    }
  }