use of org.identityconnectors.framework.spi.Connector in project midpoint by Evolveum.
the class ConnectorInstanceConnIdImpl method fetchChanges.
@Override
public UcfFetchChangesResult fetchChanges(ResourceObjectDefinition objectDefinition, UcfSyncToken initialTokenValue, AttributesToReturn attrsToReturn, Integer maxChanges, UcfExecutionContext ctx, @NotNull UcfLiveSyncChangeListener changeListener, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException {
OperationResult result = parentResult.subresult(OP_FETCH_CHANGES).addArbitraryObjectAsContext("objectClass", objectDefinition).addArbitraryObjectAsParam("initialToken", initialTokenValue).build();
try {
SyncToken initialToken = TokenUtil.toConnId(initialTokenValue);
LOGGER.trace("Initial token: {}", initialToken == null ? null : initialToken.getValue());
ResourceObjectClassDefinition objectClassDefinition = objectDefinition != null ? objectDefinition.getObjectClassDefinition() : null;
// get icf object class
ObjectClass requestConnIdObjectClass;
if (objectClassDefinition == null) {
requestConnIdObjectClass = ObjectClass.ALL;
} else {
requestConnIdObjectClass = objectClassToConnId(objectClassDefinition);
}
OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder();
if (objectDefinition != null) {
convertToIcfAttrsToGet(objectDefinition, attrsToReturn, optionsBuilder);
}
OperationOptions options = optionsBuilder.build();
AtomicInteger deltasProcessed = new AtomicInteger(0);
Thread callerThread = Thread.currentThread();
SyncDeltaConverter changeConverter = new SyncDeltaConverter(this, objectDefinition);
AtomicBoolean allChangesFetched = new AtomicBoolean(true);
UcfFetchChangesResult fetchChangesResult;
OperationResult connIdResult = result.subresult(ConnectorFacade.class.getName() + ".sync").addContext("connector", connIdConnectorFacade.getClass()).addArbitraryObjectAsParam("objectClass", requestConnIdObjectClass).addArbitraryObjectAsParam("initialToken", initialToken).build();
try {
InternalMonitor.recordConnectorOperation("sync");
ConnIdOperation operation = recordIcfOperationStart(ctx, ProvisioningOperation.ICF_SYNC, objectDefinition);
/*
* We assume that the only way how changes are _not_ fetched is that we explicitly tell ConnId to stop
* fetching them by returning 'false' from the handler.handle() method. (Or an exception occurs in the sync()
* method.)
*
* In other words, we assume that if we tell ConnId to continue feeding changes to us, we are sure that on
* successful exit from sync() method all changes were processed.
*/
SyncResultsHandler syncHandler = syncDelta -> {
Thread handlingThread = Thread.currentThread();
if (!handlingThread.equals(callerThread)) {
LOGGER.warn("Live Sync changes are being processed in a thread {} that is different from the invoking one ({}). " + "This can cause issues e.g. with operational statistics reporting.", handlingThread, callerThread);
}
recordIcfOperationSuspend(ctx, operation);
LOGGER.trace("Received sync delta: {}", syncDelta);
OperationResult handleResult;
// But - just for sure - let us create subresults in a safe way.
synchronized (connIdResult) {
handleResult = connIdResult.subresult(OP_FETCH_CHANGES + ".handle").addArbitraryObjectAsParam("uid", syncDelta.getUid()).setMinor().build();
}
UcfLiveSyncChange change = null;
try {
// Here we again assume we are called in a single thread, and that changes received here are in
// the correct order - i.e. in the order in which they are to be processed.
int sequentialNumber = deltasProcessed.incrementAndGet();
change = changeConverter.createChange(sequentialNumber, syncDelta, handleResult);
// The following should not throw any exceptions
boolean canContinue = changeListener.onChange(change, handleResult);
boolean doContinue = canContinue && canRun(ctx) && (maxChanges == null || maxChanges == 0 || sequentialNumber < maxChanges);
if (!doContinue) {
allChangesFetched.set(false);
}
return doContinue;
} catch (RuntimeException e) {
handleResult.recordFatalError(e);
// any exception here is not expected
LoggingUtils.logUnexpectedException(LOGGER, "Got unexpected exception while handling live sync " + "change, stopping the processing. Sync delta: {}, UCF change: {}", e, syncDelta, change);
return false;
} finally {
// Asynchronously processed changes (if used) have their own, separate, operation results
// that are tied to the lightweight asynchronous task handlers in ChangeProcessingCoordinator.
//
// So we can safely compute/cleanup/summarize results here.
handleResult.computeStatusIfUnknown();
handleResult.cleanupResult();
connIdResult.summarize(true);
recordIcfOperationResume(ctx, operation);
}
};
LOGGER.trace("Invoking ConnId sync operation: {}", operation);
SyncToken finalToken;
try {
finalToken = connIdConnectorFacade.sync(requestConnIdObjectClass, initialToken, syncHandler, options);
// Note that finalToken value is not quite reliable. The SyncApiOp documentation is not clear on its semantics;
// it is only from SyncTokenResultsHandler (SPI) documentation and SyncImpl class that we know this value is
// non-null when all changes were fetched. And some of the connectors return null even then.
LOGGER.trace("connector sync method returned: {}", finalToken);
connIdResult.computeStatus();
connIdResult.cleanupResult();
connIdResult.addReturn(OperationResult.RETURN_COUNT, deltasProcessed.get());
recordIcfOperationEnd(ctx, operation, null);
} catch (Throwable ex) {
recordIcfOperationEnd(ctx, operation, ex);
Throwable midpointEx = processConnIdException(ex, this, connIdResult);
connIdResult.computeStatusIfUnknown();
connIdResult.cleanupResult();
result.computeStatus();
// Do some kind of acrobatics to do proper throwing of checked exception
if (midpointEx instanceof CommunicationException) {
throw (CommunicationException) midpointEx;
} else if (midpointEx instanceof GenericFrameworkException) {
throw (GenericFrameworkException) midpointEx;
} else if (midpointEx instanceof SchemaException) {
throw (SchemaException) midpointEx;
} else if (midpointEx instanceof RuntimeException) {
throw (RuntimeException) midpointEx;
} else if (midpointEx instanceof Error) {
throw (Error) midpointEx;
} else {
throw new SystemException("Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex);
}
}
if (!canRun(ctx)) {
result.recordStatus(OperationResultStatus.SUCCESS, "Interrupted by task suspension");
}
if (allChangesFetched.get()) {
// We might consider finalToken value here. I.e. it it's non null, we could declare all changes to be fetched.
// But as mentioned above, this is not supported explicitly in SyncApiOp. So let's be a bit conservative.
LOGGER.trace("All changes were fetched; with finalToken = {}", finalToken);
fetchChangesResult = new UcfFetchChangesResult(true, TokenUtil.toUcf(finalToken));
} else {
fetchChangesResult = new UcfFetchChangesResult(false, null);
}
} catch (Throwable t) {
connIdResult.recordFatalError(t);
throw t;
} finally {
connIdResult.computeStatusIfUnknown();
}
result.recordSuccess();
result.addReturn(OperationResult.RETURN_COUNT, deltasProcessed.get());
return fetchChangesResult;
} catch (Throwable t) {
result.recordFatalError(t);
throw t;
} finally {
result.computeStatusIfUnknown();
}
}
Aggregations