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:
- A: Wrap it into an exception and mark it as PRE_OPERATION, OPERATION, [POST_OPERATION] exception and throw it up to IDM.
- 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
Generic rules
Framework API level:
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
Connector SPI level:
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.
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
javax.security.auth.login.LoginException
- javax.security.auth.login.AccountException
- javax.security.auth.login.AccountExpiredException
- javax.security.auth.login.AccountLockedException
- javax.security.auth.login.AccountNotFoundException
- javax.security.auth.login.CredentialException
- javax.security.auth.login.CredentialExpiredException
- javax.security.auth.login.CredentialNotFoundException
- javax.security.auth.login.FailedLoginException
org.identityconnectors.framework.common.exceptions.ConnectorSecurityException
- org.identityconnectors.framework.common.exceptions.InvalidCredentialException
- org.identityconnectors.framework.common.exceptions.InvalidPasswordException
- org.identityconnectors.framework.common.exceptions.PasswordExpiredException
- org.identityconnectors.framework.common.exceptions.UnknownUidException
- org.identityconnectors.framework.common.exceptions.InvalidPasswordException
- org.identityconnectors.framework.common.exceptions.PermissionDeniedException
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:
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.