Exception Management

Radovan pointed out a very important problem with the origin of the Exception.

The full call chain may have two I/O channels and if both are network connection then very likely they throw the same exception and IDM can not distinguish between them.

[IDM] --I/O--> [ConnectorServer] --I/O--> [Resource]

We have full control over the network connection between the IDM and ConnectorServer and we can catch any Throwable and as I propose the Framework can call the appropriate method on a callback handler instead of wrap them into ConnectorIOException and label them as "Come from ConnectorServer IO" Exception. We can expect any kind of exception from the second I/O between the Connector and the Resource. IDM needs to know if the connection to ConnectorServer fails or the connection from connector to Resource fails. In first case IDM can try another ConnectorServer in second case it's a Exception blocking the operation. The problem may be Retryable so it's one case where we can use a RetryableException and wrap the original exception.


The other important fact is where in the call stack the exceptions comes from. The API operation call goes through these steps and any of them may throw exception and we can not identify the source.

  • validate
  • init
  • checkAlive
  • operation
  • dispose

If validate throws IllegalArgumentException then the configuration is wrong and it's a permanent exception, if the create operation throws IllegalArgumentException then the request contains something wrong. Both may be handled differently in IDM.

Validation Failed -> Permanent Exception because the configuration is invalid.
Init or CheckAlive Failed -> Retryable/Permanent Exception because the remote server may be down at the moment. It's up to IDM to decide. The connector implementor can use the RetryableException here too.
Operation Failed -> Permanent Exception because the bad request/attributes was supplied or Permission is denied or Operation is not supported. IDM may handle different exceptions in different way.
Dispose Failed -> Diagnostic report to IDM because the allocated resources has to be discharged at some point.


The pool exceptions may be interesting to IDM and there is one where we may change
ConnectorException - If the pool has no more available Connector after the {@link ObjectPoolConfiguration#maxWait} time.
to
RetryableException - If the pool has no more available Connector after the {@link ObjectPoolConfiguration#maxWait} time.


One solution to avoid some problem is when we can force IDM to call the validate and be sure when we call the operation (for example create) the validate won't fail. Still there is init, checkAlive and after a success operation the dispose and all these may be over any I/O connection.

  • Connect to remote server [OK/Fail] --> validate[OK/Fail]
  • Connect to remote server [OK/Fail] --> validate[OK] --> init[OK/Fail] --> checkAlive[OK/Fail] --> operation[OK/Fail] --> dispose [OK/Fail]

Q: What if Validate throws NullPointerException/IllegalArgumentException or the operation throws it? I think this is why it's important to separate them.

Solutions:

  1. A: Wrap it into an exception and mark it as PRE_OPERATION, OPERATION, [POST_OPERATION] exception and throw it up to IDM.
  2. B: Change the API to use a CallBackHandler and framework can call the appropriate method with the exception.


I prefer the 2. B option because that is closer the to the solution proposed for the query and sync implementation. Now the Framework implementation has a synchronous API then asynchronous implementation and then the API is a mix of sync/async. When we want to support the Asynchronous provisioning in the future, this interface change makes it simpler for the IDM integration.

import org.identityconnectors.framework.common.objects.ConnectorObject;
/**
 *
 */
public interface ResultHandler<V> {
    /**
     *
     */
    public enum Origin {
        /**
         * The Origin of the exception is the connection to the Connector Server.
         */
        CONNECTION,
        /**
         * The Origin of Exception is before the Framework calls the Operation.
         * validate or init or checkAlive
         */
        PRE_OPERATION,
        /**
         * The Origin of Exception is inside the Operation.
         */
        OPERATION,
        /**
         * The Origin of Exception is after the Framework called the Operation.
         * dispose
         */
        POST_OPERATION,
    }
    /**
     * Invoked when the asynchronous request has failed.
     *
     * @param origin
     *            The origin of the exception.
     * @param error
     *            The resource exception indicating why the asynchronous request
     *            has failed.
     */
    void handleError(Origin origin, Throwable error);
    
    /**
     * Invoked when the asynchronous request has completed successfully.
     *
     * @param result
     *            The query result indicating that no more resources are to be
     *            returned and, if applicable, including information which
     *            should be used for subsequent paged results query requests.
     */
    void handleResult(V result);
}

 


There is another issue with the Exception serialisation. I noticed nobody knows well how the exceptions are handled if they are serialised and received from a remote server. For the better understanding the problem and the solution I wrote this sample project and I documented so you can play with it and see What exception is received in IDM if a remote connector throws a NumberFormatException and what happens with the original cause and the stack trace. I think if you see it as a Java code it tells you more then I can explain the problem.

From the log you can see if a remote java connector throws AssertionError then IDM receives RuntimeException.

Call [facade ]:
Throw [Error ]
Catch [remote]: class="AssertionError" message=Missing attribute cause=null
Catch [IDM ]: class="RuntimeException" message=Missing attribute cause=null

From the log you can see if a remote .Net connector throws Microsoft.Build.BuildEngine.InvalidToolsetDefinitionException then IDM receives RuntimeException.

Call [facade]:
Throw [UnknownException]
Catch [dotnet]: class="Microsoft".Build.BuildEngine.InvalidToolsetDefinitionException message=Example Message cause=null
Catch [IDM ]: class="RuntimeException" message=Microsoft.Build.BuildEngine.InvalidToolsetDefinitionException cause=null


We need to bring together the .Net System.Exception and the Java Error/Exception/RuntimeException management and the the way they are serialised in binary/xml format without loosing important information.

Because ConnId targets Java 6 and OpenICF try to stay Java 5 compatible I listed here the Java 5 RuntimeExceptions and marked the Java6. I check but I didn't see any new required Java 6 exceptions so I suggest to stay with Java 5.

I listed and I categorised the exceptions in JVM.

FU - Fully Used: These exceptions should be part of the contract. They are named Known exceptions in the sample project.
NU - Not Used in the contract. If this exception is thrown then it was downcast to plain new RuntimeException(e.getMessage())

Smart Message: The exception has more information then just e.getMessage() so concatenating them would contains all information. This would requires additional logic per exception. Q: Does it worth it?
Complex: The Class does not have Default or String constructor.
Null message: The Exception does not have Message (Exception )
Subclasses: The Exception has many other subclasses


AnnotationTypeMismatchException, NU Smart Message
ArithmeticException, NU
ArrayStoreException, NU
BufferOverflowException, --> System.IO.InternalBufferOverflowException NU
BufferUnderflowException, NU Null message
CannotRedoException, NU Null message
CannotUndoException, NU Null message
ClassCastException, --> System.InvalidCastException NU
CMMException, NU
ConcurrentModificationException, NU
Java 6 DataBindingException
DOMException, NU Smart Message
EmptyStackException, NU Null message
EnumConstantNotPresentException, NU Smart Message
EventException, NU Smart Message
IllegalArgumentException, --> System.ArgumentException FU Subclasses
IllegalMonitorStateException, NU
IllegalPathStateException, NU
IllegalStateException, --> System.InvalidOperationException -- FU/ NU? Subclasses
ImagingOpException, NU
IncompleteAnnotationException, NU Smart Message
IndexOutOfBoundsException, NU
JMRuntimeException, NU
LSException, NU Smart Message
MalformedParameterizedTypeException, NU Null message
Java 6 MirroredTypeException,
Java 6 MirroredTypesException
MissingResourceException, NU Smart Message
NegativeArraySizeException, NU
NoSuchElementException, NU
Java 6 NoSuchMechanismException,
NullPointerException, --> System.NullReferenceException FU (don't confuse with System.ArgumentNullException extends System.ArgumentException :( )
ProfileDataException, NU
ProviderException, NU
RasterFormatException, NU
RejectedExecutionException, NU
SecurityException --> System.Security.SecurityException
SystemException, Too complex, use the message only
Java 6 TypeConstraintException
TypeNotPresentException, NU, Complex
UndeclaredThrowableException, NU Complex
Java 6 UnknownAnnotationValueException,
Java 6 UnknownElementException,
Java 6 UnknownTypeException
UnmodifiableSetException, NU
UnsupportedOperationException, --> System.NotSupportedException FU
Java 6 WebServiceException


Exceptions

AclNotFoundException, NU Null message
ActivationException,NU
AlreadyBoundException,NU
ApplicationException, CORBA Too Complex
AWTException, NU
BackingStoreException,NU
BadAttributeValueExpException, NU Complex
BadBinaryOpValueExpException, NU Complex
BadLocationException, NU Complex
BadStringOperationException,NU
BrokenBarrierException,NU
CertificateException,NU
ClassNotFoundException, NU
CloneNotSupportedException,NU
DataFormatException,NU
DatatypeConfigurationException,NU
DestroyFailedException,NU
ExecutionException,NU
ExpandVetoException, Complex
FontFormatException,NU
GeneralSecurityException, FU Subclass
GSSException, Too Complex
IllegalAccessException,NU
IllegalClassFormatException,NU
InstantiationException,NU
InterruptedException,NU
IntrospectionException,EE
InvalidApplicationException, NU Complex
InvalidMidiDataException,NU
InvalidPreferencesFormatException,NU
InvalidTargetObjectTypeException,NU
InvocationTargetException,NU
IOException, NU SUBCLASSES
Java 6 JAXBException, NU Smart Message
JMException,NU
Java 6 KeySelectorException,NU
LastOwnerException, NU Null message
LineUnavailableException,NU
Java 6 MarshalException,NU
MidiUnavailableException,NU
MimeTypeParseException,NU
Java 6 MimeTypeParseException,NU
NamingException, NU Complex Subclasses
NoninvertibleTransformException,NU
NoSuchFieldException, NU
NoSuchMethodException,NU
NotBoundException,NU
NotOwnerException, NU Null Message
ParseException, NU Complex
ParserConfigurationException,NU
PrinterException,NU
PrintException,NU
PrivilegedActionException, NU Too Complex
PropertyVetoException, NU Complex
RefreshFailedException,NU
RemarshalException,NU
RuntimeException, FU Subclasses
SAXException, NU Subclasses
Java 6 ScriptException,NU
ServerNotActiveException,NU
Java 6 SOAPException,NU
SQLException, NU Subclasses
TimeoutException,NU
TooManyListenersException,NU
TransformerException, NU Smart Message
Java 6 TransformException,NU
UnmodifiableClassException,NU
UnsupportedAudioFileException,NU
UnsupportedCallbackException,NU
UnsupportedFlavorException, NU Complex
UnsupportedLookAndFeelException, NU
Java 6 URIReferenceException, NU Smart Message
URISyntaxException, NU Complex
UserException, NU Corba, Subclasses
XAException,NU
XMLParseException,NU
Java 6 XMLSignatureException,NU
Java 6 XMLStreamException, NU Smart Message
XPathException NU Subclasses

Proposed new Exceptions

SchemaViolationException

PreconditionFailedException

PreconditionRequiredException

RetryableException

PasswordNotFoundException

InvalidAccountException

AccountExpiredException

AccountLockedException

Proposed JavaDoc update

These are the proposed rules to follow when a new Connector is developed. We can copy the documentation from here to JavaDoc when everyone is agreed.

Generic rules

These rules are common for all API or SPI level operations:

Framework API level:

IllegalArgumentException or NullPointerException - when ObjectClass is null or the name is blank.
OperationTimeoutException - when the wait timed out.ConnectorIOException, ConnectionBrokenException, ConnectionFailedException - if any problem occurs with the connector server connection. 

UnsupportedOperationException - If the Connector does not implement the required interface

 

ConnectorIOException - if failed to init remote connection because a SocketException 
ConnectorException - if failed to init remote connection and SocketException 
ConnectorException - if Unexpected request was sent to remote connector server  
ConnectorException - if Unexpected response received from remote connector server
InvalidCredentialException - if Remote framework key is invalid

 

The pool exceptions may be interesting to IDM.
ConnectorException - If the pool has no more available Connector after the {@link ObjectPoolConfiguration#maxWait} time.
IllegalStateException - If Object pool already shutdown.

Connector SPI level:

IllegalArgumentException / SchemaViolationException/ when non MULTIVALUED {@link Attribute} has multiple values. 
IllegalArgumentException when the value of  __PASSWORD__ or the __CURRENT_PASSWORD__ Attribute is not GuardedString.
IllegalStateException when the name of {@link Attribute} is blank.
PermissionDeniedException - 
If the target resource will not allow to perform a particular operation. An instance of {@code PermissionDeniedException} generally describes a native error returned by (or wraps a native exception thrown by) the target resource.
ConnectorIOException, ConnectionBrokenException, ConnectionFailedException - if any problem occurs with the resource connection.
PreconditionFailedException - if resource objects's current version does not match the version provided.
PreconditionRequiredException - if a resource object requires a version, but no version was supplied in the {@link Uid#getRevision}  

Authenticate

These rules are only for the Authenticate Operation:

AuthenticationApiOp

These rules are for API level:
RuntimeException - if the credentials do not pass authentication otherwise nothing. 

AuthenticateOp

These rules are for SPI level:
UnknownUidException - when the {@link Uid} does not exist on the resource. 
ConnectorSecurityException - 
InvalidCredentialException
InvalidPasswordException

PasswordExpiredException
The proposition to make the Framework compatible to implement a JSR-196 login module. Find a convenient mapping between the two exception set.

javax.security.auth.login.LoginException

 

org.identityconnectors.framework.common.exceptions.ConnectorSecurityException

 

Proposed mapping:

AccountException <-- InvalidAccountException
AccountExpiredException <-- AccountExpiredException
AccountLockedException <-- AccountLockedException
AccountNotFoundException <-- UnknownUidException
CredentialException <-- InvalidPasswordException
CredentialExpiredException <-- PasswordExpiredException
CredentialNotFoundException <-- PasswordNotFoundException
FailedLoginException <-- InvalidCredentialException

Create

These rules are only for the Create Operation:

CreateApiOp

These rules are for API level:
IllegalArgumentException - if {@code ObjectClass} is missing or elements of the set produce duplicate values of {@link Attribute#getName()}.
NullPointerException -  if the parameter <code>createAttributes</code> is <code>null</code>.
RuntimeException - if the {@link Connector} SPI throws a native  {@link Exception}

CreateOp

These rules are for SPI level:

 UnsupportedOperationException - if the Create operation on given ObjectClass is not supported.
 SchemaViolationException - if required attribute is missing, not createable attribute is present or attribute has invalid value.
 AlreadyExistsException - If the object with the given Name already exits.
 PermissionDeniedException - If  the target resource will not allow a Connector to perform a particular operation.
 ConnectorIOException, ConnectionBrokenException, ConnectionFailedException - if any problem occurs with the connection.
 RuntimeException - If anything goes wrong. Try to throw native exception 

Delete

These rules are only for the Delete Operation:

DeleteApiOp

These rules are for API level:

DeleteOp

These rules are for SPI level:
UnknownUidException - when the {@link Uid} does not exist on the resource.

ResolveUsername

These rules are only for the ResolveUsername Operation:

ResolveUsernameApiOp

These rules are for API level:

ResolveUsernameOp

These rules are for SPI level:
UnknownUidException - when the {@link Uid} does not exist on the resource. 

Schema

These rules are only for the Schema Operation:

SchemaApiOp

These rules are for API level:

SchemaOp

These rules are for SPI level:

ScriptOnConnector

These rules are only for the ScriptOnConnector Operation:

ScriptOnConnectorApiOp

These rules are for API level:

ScriptOnConnectorOp

These rules are for SPI level:

ScriptOnResource

These rules are only for the ScriptOnResource Operation:

ScriptOnResourceApiOp

These rules are for API level:

ScriptOnResourceOp

These rules are for SPI level:

Read

These rules are only for the Read Operation:

GetApiOp

These rules are for API level:
UnknownUidException - when the {@link Uid} does not exist on the resource. 

SearchApiOp

These rules are for API level:

SearchOp

These rules are for SPI level:

Sync

These rules are only for the Sync Operation:

SyncApiOp

These rules are for API level:

sync

IllegalArgumentException -  if {@code objectClass} or {@code handler} is null or if any argument is invalid.

getLatestSyncToken

IllegalArgumentException - if {@code objectClass} is null or is invalid.

 

SyncOp

These rules are for SPI level:

Test

These rules are only for the Test Operation:

TestApiOp

These rules are for API level:

TestOp

These rules are for SPI level:

RuntimeException - if the configuration is not valid or the test failed. Implementations are encouraged to throw the most specific exception available. When no specific exception is available, implementations can throw {@link ConnectorException}.

Update

These rules are only for the Update Operation:

UpdateApiOp

These rules are for API level:
UnknownUidException - if the {@link Uid} does not exist on the resource and the connector does not implement the {@link UpdateAttributeValuesOp} interface. 

UpdateAttributeValuesOp

These rules are for SPI level:
UnknownUidException - when the {@link Uid} does not exist on the resource. 

UpdateOp

These rules are for SPI level:
UnknownUidException - when the {@link Uid} does not exist on the resource. 

Validate

These rules are only for the Validate Operation:

ValidateApiOp

These rules are for API and the Facade initialisation level:

ConfigurationException - when the required Framework version is not compatible with the connector or the Connector does not have required attributes in MANIFEST.MF or when the ConfigurationProperties can not merge into the Configuration.

AbstractConfiguration

These rules are for SPI level:

RuntimeException - when the configuration is not valid.

NullPointerException - when required configuration property is null.
IllegalArgumentException - when required configuration property is blank.