Exception handling

From the ticket  INSPECTIT-1925 - Getting issue details... STATUS  inspectIT introduced a specific way of dealing with exceptions that can occur during service calls. Developers should follow the general idea that will be presented on this page, so that we have a constant way of exception handling throughout complete server component.

Checked exceptions

There are two main exception classes that deal with checked exceptions, based on the type of the exception:

  • Business exception
  • Technical exception

Business exception

Business exceptions should be used when a specific business related exception should be raised. For example, the business logic is not allowing any writes to be done on the finalized storage, thus if such write is attempted BusinessException should be thrown. The business exception has two constructors that can be used:

  1. BusinessException(IErrorCode errorCode)
  2. BusinessException(String actionPerformed, IErrorCode errorCode)

As it can be seen the error code must always be given. Error code is giving more information about the business exception that is raised, like what component it is related to, what can be possible causes and solutions for the exception, etc. Currently there are two enumerations (AgentManagementErrorCodeEnum & StorageErrorCodeEnum) that are defining different error codes. Idea is to have one error code enumeration per component, thus developers are free to create new enumerations if needed.

The actionPerformed string is an optional parameter to the business exception that can be used to further describe the exception. Following the write to finalized storage example, we would most likely end up with such exception construction:

// somebody tried to write to finalized storage
throw new BusinessException("Write attempted to storage XYZ", StorageErrorCodeEnum.STORAGE_ALREADY_CLOSED);
Technical exception

Technical exceptions are used when a non-expected Java checked exception is caught during the service execution. For example, a write to storage might raise the IOException. In normal cases this exception is not expected, but since our service is only defining BusinessException in throw option (see below), we must translate such checked exception. In this case we use technical exception.

public void writeToStorage() throws BusinessException{
	try {
		executeWrite();
	} catch (IOException ioException) {
		throw new TechnicalException("Write attempted to storage XYZ", StorageErrorCodeEnum.INPUT_OUTPUT_OPERATION_FAILED, ioException);
	}
}

As it can be seen the technical exception is an extended version of business exception that also sets the root exception cause.

Service methods

Note that service methods should only define BusinessException as the throwing possibility:

void myNewService() throws BusinessException;

Unchecked exceptions

The developers should not care about the unchecked exceptions that can occur during the service method execution. These exceptions will be caught with the exception interceptor (see info.novatec.inspectit.cmr.spring.aop.ExceptionInterceptor). This interceptor defines an around aspect that is surrounding all service methods:

info.novatec.inspectit.cmr.spring.aop.ExceptionInterceptor
@Around("execution(* info.novatec.inspectit.cmr.service.*.*(..))")
public Object logServiceException(ProceedingJoinPoint jp) throws Exception {
	try {
		return jp.proceed();
	} catch (BusinessException e) {
		if (log.isDebugEnabled()) {
			log.debug("BusinessException thrown in the service method " + jp.getSignature() + " executed with following parameters: " + Arrays.toString(jp.getArgs()) + ".", e);
		}
		// if it's our BusinessException set service signature and just re-throw it
		e.setServiceMethodSignature(jp.getSignature().toString());
		throw e;
	} catch (RemoteException e) {
		log.warn(
				"Exception thrown in the service method " + jp.getSignature() + " executed with following parameters: " + Arrays.toString(jp.getArgs()) + ". Original exception class is: "
						+ e.getOriginalExceptionClass(), e);
		// if we already have remote one service signature and just re-throw it
		e.setServiceMethodSignature(jp.getSignature().toString());
		throw e;
	} catch (Throwable t) { // NOPMD
		log.warn("Exception thrown in the service method " + jp.getSignature() + " executed with following parameters: " + Arrays.toString(jp.getArgs()) + ".", t);
		RemoteException transformException = transformException(t, new ArrayList<Throwable>());
		transformException.setServiceMethodSignature(jp.getSignature().toString());
		throw transformException;
	}
}

The interceptor will caught all unchecked exceptions and translate them to the info.novatec.inspectit.exception.RemoteException, that can always be (de-)serialized and thus transferred to the service method caller. 

The exception interceptor is also responsible for setting the service method signature in the business or technical exceptions, thus developers should never try to set the signature on their own.